From e0385cc47c0ffff81ca997bf48a80b73d3ec9bc0 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Fri, 8 Apr 2022 15:11:22 -0700 Subject: [PATCH] canUserUpdate build and build owner config --- core/src/routes/builds.ts | 61 ++++++++++++ .../src/components/builds/tabs/Owners.tsx | 92 +++++++++++++++++++ .../src/components/builds/tabs/Provider.tsx | 51 +++++++--- frontend/src/components/builds/tabs/Tabs.tsx | 22 ++++- .../builds/tabs/build-config/BuildConfig.tsx | 4 +- .../builds/tabs/build-config/CliBuild.tsx | 22 +++-- .../builds/tabs/build-config/Docker.tsx | 31 +++++-- .../components/builds/tabs/git-config/Git.tsx | 32 +++++-- .../builds/tabs/git-config/GitConfig.tsx | 5 +- .../builds/tabs/git-config/OnClone.tsx | 18 +++- .../deployment/tabs/config/Config.tsx | 4 +- .../deployment/tabs/config/mount-repo/Git.tsx | 26 ++++-- .../tabs/config/mount-repo/OnGit.tsx | 38 +++++--- .../tabs/config/mount-repo/RepoMount.tsx | 9 +- frontend/src/components/topbar/Account.tsx | 4 +- frontend/src/style/app.scss | 1 + frontend/src/style/index.scss | 7 ++ frontend/src/util/query.ts | 14 +++ 18 files changed, 364 insertions(+), 77 deletions(-) create mode 100644 frontend/src/components/builds/tabs/Owners.tsx diff --git a/core/src/routes/builds.ts b/core/src/routes/builds.ts index 697ea37cd..be73e8e77 100644 --- a/core/src/routes/builds.ts +++ b/core/src/routes/builds.ts @@ -1,3 +1,4 @@ +import { BUILD_OWNER_UPDATE } from "@monitor/util"; import { FastifyInstance } from "fastify"; import fp from "fastify-plugin"; @@ -22,6 +23,66 @@ const builds = fp((app: FastifyInstance, _: {}, done: () => void) => { res.send(state); } ); + + app.post( + "/api/build/:id/:owner", + { onRequest: [app.auth, app.userEnabled] }, + async (req, res) => { + // adds an owner to a build + const { id, owner } = req.params as { id: string; owner: string }; + const sender = (await app.users.findById(req.user.id))!; + if (sender.permissions! < 1) { + res.status(403); + res.send("inadequate permissions"); + return; + } + const user = await app.users.findOne({ username: owner }); + if (!user || user.permissions! < 1) { + res.status(400); + res.send("invalid user"); + return; + } + const build = await app.builds.findById(id); + if (!build) { + res.status(400); + res.send("build not found"); + return; + } + if (sender.permissions! < 2 && !build.owners.includes(sender.username)) { + res.status(403); + res.send("inadequate permissions"); + return; + } + await app.builds.updateById(id, { $push: { owners: owner } }); + app.broadcast(BUILD_OWNER_UPDATE, { buildID: id }); + res.send("owner added"); + } + ); + + app.delete( + "/api/build/:id/:owner", + { onRequest: [app.auth, app.userEnabled] }, + async (req, res) => { + // removes owner from deployment + const { id, owner } = req.params as { id: string; owner: string }; + const sender = (await app.users.findById(req.user.id))!; + if (sender.permissions! < 2) { + res.status(403); + res.send("inadequate permissions"); + return; + } + const build = await app.builds.findById(id); + if (!build) { + res.status(400); + res.send("build not found"); + return; + } + await app.builds.updateById(id, { $pull: { owners: owner } }); + app.broadcast(BUILD_OWNER_UPDATE, { buildID: id }); + res.send("owner removed"); + } + ); + done(); }); diff --git a/frontend/src/components/builds/tabs/Owners.tsx b/frontend/src/components/builds/tabs/Owners.tsx new file mode 100644 index 000000000..321c7772d --- /dev/null +++ b/frontend/src/components/builds/tabs/Owners.tsx @@ -0,0 +1,92 @@ +import { User } from "@monitor/types"; +import { Component, createEffect, createSignal, For, Show } from "solid-js"; +import { pushNotification } from "../../.."; +import { useUser } from "../../../state/UserProvider"; +import { + addOwnerToBuild, + getUsers, + removeOwnerFromBuild, +} from "../../../util/query"; +import ConfirmButton from "../../util/ConfirmButton"; +import Input from "../../util/Input"; +import Flex from "../../util/layout/Flex"; +import Grid from "../../util/layout/Grid"; +import Menu from "../../util/menu/Menu"; +import { useConfig } from "./Provider"; + +const Owners: Component<{}> = (p) => { + const { build } = useConfig(); + const { permissions, username } = useUser(); + const [userSearch, setUserSearch] = createSignal(""); + const [users, setUsers] = createSignal([]); + createEffect(() => { + if (userSearch().length > 0) { + getUsers(userSearch(), true).then((users) => { + setUsers(users.filter((user) => !build.owners.includes(user.username))); + }); + } else { + setUsers([]); + } + }); + return ( + + setUserSearch("")} + target={ + + } + content={ + <> + + {(user) => ( + { + await addOwnerToBuild(build._id!, user.username); + pushNotification("good", "owner added to deployment"); + setUserSearch(""); + }} + confirmText="add user" + > + {user.username} + + )} + + no matching users + + } + style={{ width: "12rem" }} + /> + + {(owner) => ( + +
+ {owner} + {owner === username() && " ( you )"} +
+ 1}> + { + await removeOwnerFromBuild(build._id!, owner); + pushNotification("good", "user removed from collaborators"); + }} + > + remove + + +
+ )} +
+ + ); +}; + +export default Owners; diff --git a/frontend/src/components/builds/tabs/Provider.tsx b/frontend/src/components/builds/tabs/Provider.tsx index 7e02316d8..3247f2b7a 100644 --- a/frontend/src/components/builds/tabs/Provider.tsx +++ b/frontend/src/components/builds/tabs/Provider.tsx @@ -1,23 +1,36 @@ import { Build, Update } from "@monitor/types"; -import { Component, createContext, createEffect, onCleanup, useContext } from "solid-js"; +import { + Component, + createContext, + createEffect, + onCleanup, + useContext, +} from "solid-js"; import { createStore, DeepReadonly, SetStoreFunction } from "solid-js/store"; import { ADD_UPDATE, UPDATE_BUILD } from "../../../state/actions"; import { useAppState } from "../../../state/StateProvider"; +import { useUser } from "../../../state/UserProvider"; import { getBuild } from "../../../util/query"; -type ConfigBuild = Build & { loaded: boolean; updated: boolean; saving: boolean }; +type ConfigBuild = Build & { + loaded: boolean; + updated: boolean; + saving: boolean; +}; type State = { build: DeepReadonly; setBuild: SetStoreFunction; reset: () => void; save: () => void; + userCanUpdate: () => boolean; }; const context = createContext(); export const ConfigProvider: Component<{}> = (p) => { const { ws, selected, builds } = useAppState(); + const { permissions, username } = useUser(); const [build, set] = createStore({ ...builds.get(selected.id())!, loaded: false, @@ -31,7 +44,7 @@ export const ConfigProvider: Component<{}> = (p) => { }; const load = () => { - console.log("load server"); + console.log("load build"); getBuild(selected.id()).then((build) => { set({ ...build, @@ -44,7 +57,7 @@ export const ConfigProvider: Component<{}> = (p) => { githubAccount: build.githubAccount, loaded: true, updated: false, - saving: false + saving: false, }); }); }; @@ -55,23 +68,31 @@ export const ConfigProvider: Component<{}> = (p) => { ws.send(UPDATE_BUILD, { build }); }; - const unsub = ws.subscribe( - [ADD_UPDATE], - ({ update }: { update: Update }) => { - if (update.buildID === selected.id()) { - if ([UPDATE_BUILD].includes(update.operation)) { - load(); - } - } - } - ); - onCleanup(unsub); + const unsub = ws.subscribe([ADD_UPDATE], ({ update }: { update: Update }) => { + if (update.buildID === selected.id()) { + if ([UPDATE_BUILD].includes(update.operation)) { + load(); + } + } + }); + onCleanup(unsub); + + const userCanUpdate = () => { + if (permissions() > 1) { + return true; + } else if (permissions() > 0 && build.owners.includes(username()!)) { + return true; + } else { + return false; + } + }; const state = { build, setBuild, reset: load, save, + userCanUpdate, }; return {p.children}; }; diff --git a/frontend/src/components/builds/tabs/Tabs.tsx b/frontend/src/components/builds/tabs/Tabs.tsx index dc2138a0e..42f71f1d2 100644 --- a/frontend/src/components/builds/tabs/Tabs.tsx +++ b/frontend/src/components/builds/tabs/Tabs.tsx @@ -1,16 +1,28 @@ import { Component, Show } from "solid-js"; import { useAppState } from "../../../state/StateProvider"; +import { useUser } from "../../../state/UserProvider"; import Tabs from "../../util/tabs/Tabs"; import BuildConfig from "./build-config/BuildConfig"; import GitConfig from "./git-config/GitConfig"; +import Owners from "./Owners"; import { ConfigProvider } from "./Provider"; const BuildTabs: Component<{}> = (p) => { const { builds, selected } = useAppState(); + const { username, permissions } = useUser(); const build = () => builds.get(selected.id())!; + const userCanUpdate = () => { + if (permissions() > 1) { + return true; + } else if (permissions() > 0 && build().owners.includes(username()!)) { + return true; + } else { + return false; + } + }; return ( - + = (p) => { }, { title: "build", - element: - } + element: , + }, + userCanUpdate() && { + title: "collaborators", + element: , + }, ]} localStorageKey="build-tab" /> diff --git a/frontend/src/components/builds/tabs/build-config/BuildConfig.tsx b/frontend/src/components/builds/tabs/build-config/BuildConfig.tsx index a10387731..e56145ab1 100644 --- a/frontend/src/components/builds/tabs/build-config/BuildConfig.tsx +++ b/frontend/src/components/builds/tabs/build-config/BuildConfig.tsx @@ -9,7 +9,7 @@ import { useConfig } from "../Provider"; import Loading from "../../../util/loading/Loading"; const BuildConfig: Component<{}> = (p) => { - const { build, reset, save } = useConfig(); + const { build, reset, save, userCanUpdate } = useConfig(); return ( @@ -17,7 +17,7 @@ const BuildConfig: Component<{}> = (p) => { - + = (p) => { - const { build, setBuild } = useConfig(); + const { build, setBuild, userCanUpdate } = useConfig(); return (

cli build

-
build with a custom command
- -
build path
+ {/*
build with a custom command
*/} + +

build path:

setBuild("cliBuild", { path })} + disabled={!userCanUpdate()} />
- -
command
+ +

command:

setBuild("cliBuild", { command })} + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/builds/tabs/build-config/Docker.tsx b/frontend/src/components/builds/tabs/build-config/Docker.tsx index 22a31f531..86ef95ecd 100644 --- a/frontend/src/components/builds/tabs/build-config/Docker.tsx +++ b/frontend/src/components/builds/tabs/build-config/Docker.tsx @@ -10,31 +10,45 @@ import { useConfig } from "../Provider"; const Docker: Component<{}> = (p) => { const { dockerAccounts } = useAppState(); - const { build, setBuild } = useConfig(); + const { build, setBuild, userCanUpdate } = useConfig(); return (

docker build

{/* checkbox here? */} - -
build path
+ +

build path:

setBuild("dockerBuildArgs", { buildPath })} + disabled={!userCanUpdate()} />
- -
dockerfile path
+ +

dockerfile path:

setBuild("dockerBuildArgs", { dockerfilePath }) } + disabled={!userCanUpdate()} />
0}> - -
account
+ +

account:

= (p) => { ); }} position="bottom right" + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/builds/tabs/git-config/Git.tsx b/frontend/src/components/builds/tabs/git-config/Git.tsx index c4d084362..1245fff15 100644 --- a/frontend/src/components/builds/tabs/git-config/Git.tsx +++ b/frontend/src/components/builds/tabs/git-config/Git.tsx @@ -1,5 +1,4 @@ -import { Component, Show } from "solid-js"; -import { combineClasses } from "../../../../util/helpers"; +import { Component, createEffect, Show } from "solid-js"; import Grid from "../../../util/layout/Grid"; import { useConfig } from "../Provider"; import Flex from "../../../util/layout/Flex"; @@ -9,29 +8,41 @@ import Selector from "../../../util/menu/Selector"; const Git: Component<{}> = (p) => { const { githubAccounts } = useAppState(); - const { build, setBuild } = useConfig(); + const { build, setBuild, userCanUpdate } = useConfig(); + createEffect(() => console.log(build.branch)); return (

github config

- -
repo
+ +

repo:

setBuild("repo", value)} + disabled={!userCanUpdate()} />
- -
branch
+ +

branch:

setBuild("branch", value)} + disabled={!userCanUpdate()} />
0}> - -
github account
+ +

github account:

= (p) => { ); }} position="bottom right" + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/builds/tabs/git-config/GitConfig.tsx b/frontend/src/components/builds/tabs/git-config/GitConfig.tsx index f06bbd4fc..4b2142b60 100644 --- a/frontend/src/components/builds/tabs/git-config/GitConfig.tsx +++ b/frontend/src/components/builds/tabs/git-config/GitConfig.tsx @@ -1,5 +1,4 @@ import { Component, Show } from "solid-js"; -import { combineClasses } from "../../../../util/helpers"; import ConfirmButton from "../../../util/ConfirmButton"; import Icon from "../../../util/Icon"; import Flex from "../../../util/layout/Flex"; @@ -10,7 +9,7 @@ import Git from "./Git"; import OnClone from "./OnClone"; const GitConfig: Component<{}> = (p) => { - const { build, reset, save } = useConfig(); + const { build, reset, save, userCanUpdate } = useConfig(); return ( @@ -18,7 +17,7 @@ const GitConfig: Component<{}> = (p) => { - + { - const { build, setBuild } = useConfig(); + const { build, setBuild, userCanUpdate } = useConfig(); return (

on clone

- - path: + +

path:

{ } setBuild("onClone", { path }); }} + disabled={!userCanUpdate()} />
- - command: + +

command:

{ } setBuild("onClone", { command }); }} + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/deployment/tabs/config/Config.tsx b/frontend/src/components/deployment/tabs/config/Config.tsx index 3c46c3def..e0ade758a 100644 --- a/frontend/src/components/deployment/tabs/config/Config.tsx +++ b/frontend/src/components/deployment/tabs/config/Config.tsx @@ -44,7 +44,7 @@ const Config: Component<{}> = (p) => {
), }, - { + (userCanUpdate() || deployment.repo ? true : false) && { title: "repo mount", element: ( @@ -58,7 +58,7 @@ const Config: Component<{}> = (p) => { userCanUpdate() && { title: "collaborators", element: ( - + ), diff --git a/frontend/src/components/deployment/tabs/config/mount-repo/Git.tsx b/frontend/src/components/deployment/tabs/config/mount-repo/Git.tsx index 1cab22419..e8ee26196 100644 --- a/frontend/src/components/deployment/tabs/config/mount-repo/Git.tsx +++ b/frontend/src/components/deployment/tabs/config/mount-repo/Git.tsx @@ -8,29 +8,40 @@ import { useConfig } from "../Provider"; const Git: Component<{}> = (p) => { const { githubAccounts } = useAppState(); - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return (

github config

- -
repo
+ +

repo:

setDeployment("repo", value)} + disabled={!userCanUpdate()} />
- -
branch
+ +

branch:

setDeployment("branch", value)} + disabled={!userCanUpdate()} />
0}> - -
github account
+ +

github account:

= (p) => { ); }} position="bottom right" + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/deployment/tabs/config/mount-repo/OnGit.tsx b/frontend/src/components/deployment/tabs/config/mount-repo/OnGit.tsx index 152067d28..a145c66fa 100644 --- a/frontend/src/components/deployment/tabs/config/mount-repo/OnGit.tsx +++ b/frontend/src/components/deployment/tabs/config/mount-repo/OnGit.tsx @@ -5,15 +5,18 @@ import Grid from "../../../../util/layout/Grid"; import { useConfig } from "../Provider"; export const OnClone: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return (

on clone

- - path: + +

path:

{ if ( path.length === 0 && @@ -25,10 +28,14 @@ export const OnClone: Component<{}> = (p) => { } setDeployment("onClone", { path }); }} + disabled={!userCanUpdate()} />
- - command: + +

command:

= (p) => { } setDeployment("onClone", { command }); }} + disabled={!userCanUpdate()} />
@@ -50,12 +58,15 @@ export const OnClone: Component<{}> = (p) => { }; export const OnPull: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return (

on pull

- - path: + +

path:

= (p) => { } setDeployment("onPull", { path }); }} + disabled={!userCanUpdate()} />
- - command: + +

command:

= (p) => { } setDeployment("onPull", { command }); }} + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/deployment/tabs/config/mount-repo/RepoMount.tsx b/frontend/src/components/deployment/tabs/config/mount-repo/RepoMount.tsx index de068d372..5f09a3f08 100644 --- a/frontend/src/components/deployment/tabs/config/mount-repo/RepoMount.tsx +++ b/frontend/src/components/deployment/tabs/config/mount-repo/RepoMount.tsx @@ -5,16 +5,20 @@ import Grid from "../../../../util/layout/Grid"; import { useConfig } from "../Provider"; const RepoMount: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return (

mount

- + setDeployment("repoMount", value)} + disabled={!userCanUpdate()} /> {" : "} = (p) => { value={deployment.containerMount || ""} style={{ width: "40%" }} onEdit={(value) => setDeployment("containerMount", value)} + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/topbar/Account.tsx b/frontend/src/components/topbar/Account.tsx index 1106085e7..224fc38a9 100644 --- a/frontend/src/components/topbar/Account.tsx +++ b/frontend/src/components/topbar/Account.tsx @@ -25,9 +25,9 @@ const Account: Component<{ close: () => void }> = (p) => { manage users
- + ); }; diff --git a/frontend/src/style/app.scss b/frontend/src/style/app.scss index f57931ce8..004cbbbb6 100644 --- a/frontend/src/style/app.scss +++ b/frontend/src/style/app.scss @@ -53,6 +53,7 @@ } .config-items { + height: fit-content; max-height: 65vh; padding: 0rem 0.5rem; } diff --git a/frontend/src/style/index.scss b/frontend/src/style/index.scss index 7fbdb0136..6bb818775 100644 --- a/frontend/src/style/index.scss +++ b/frontend/src/style/index.scss @@ -23,6 +23,13 @@ h1 { margin: 0; } +h2 { + font-size: 1.1rem; + font-weight: 450; + user-select: none; + margin: 0; +} + button { color: inherit; padding: 0.5rem; diff --git a/frontend/src/util/query.ts b/frontend/src/util/query.ts index 9ed8d3a73..e90025e1a 100644 --- a/frontend/src/util/query.ts +++ b/frontend/src/util/query.ts @@ -39,6 +39,20 @@ export async function getBuildActionState(buildID: string) { ); } +export async function addOwnerToBuild( + buildID: string, + username: string +) { + return await client.post(`/api/build/${buildID}/${username}`); +} + +export async function removeOwnerFromBuild( + buildID: string, + username: string +) { + return await client.delete(`/api/build/${buildID}/${username}`); +} + export async function getDeployments(query?: { serverID?: string }) { return await client.get( "/api/deployments" + generateQuery(query)