diff --git a/core/src/plugins/auth/github.ts b/core/src/plugins/auth/github.ts index 05e4acb70..26659056e 100644 --- a/core/src/plugins/auth/github.ts +++ b/core/src/plugins/auth/github.ts @@ -1,3 +1,4 @@ +import { USER_UPDATE } from "@monitor/util"; import axios from "axios"; import { FastifyInstance } from "fastify"; import fastifyOauth2, { OAuth2Namespace } from "fastify-oauth2"; @@ -43,6 +44,7 @@ const github = fp((app: FastifyInstance, _: {}, done: () => void) => { ); } else { const createdUser = await app.users.create(profile); + app.broadcast(USER_UPDATE, {}); const jwt = app.jwt.sign( { id: createdUser._id!.toString() }, { expiresIn: TOKEN_EXPIRES_IN } diff --git a/core/src/plugins/auth/local.ts b/core/src/plugins/auth/local.ts index b623a9789..365fe3145 100644 --- a/core/src/plugins/auth/local.ts +++ b/core/src/plugins/auth/local.ts @@ -1,4 +1,5 @@ import { User } from "@monitor/types"; +import { USER_UPDATE } from "@monitor/util"; import { compare, hash } from "bcrypt"; import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import fp from "fastify-plugin"; @@ -18,6 +19,7 @@ const local = fp((app: FastifyInstance, _: {}, done: () => void) => { username, password: hashedPass, }); + app.broadcast(USER_UPDATE, {}); const jwt = app.jwt.sign( { id: user._id!.toString() }, { expiresIn: TOKEN_EXPIRES_IN } diff --git a/frontend/src/components/deployment/tabs/config/DockerAccount.tsx b/frontend/src/components/deployment/tabs/config/DockerAccount.tsx index 45b60c2ec..126531acd 100644 --- a/frontend/src/components/deployment/tabs/config/DockerAccount.tsx +++ b/frontend/src/components/deployment/tabs/config/DockerAccount.tsx @@ -6,12 +6,13 @@ import { useConfig } from "./Provider"; const DockerAccount: Component<{}> = (p) => { const { dockerAccounts } = useAppState(); - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return ( 0}>

docker account

= (p) => { ) } position="bottom right" + disabled={!userCanUpdate()} />
diff --git a/frontend/src/components/deployment/tabs/config/Env.tsx b/frontend/src/components/deployment/tabs/config/Env.tsx index 88639c036..d0e8d552b 100644 --- a/frontend/src/components/deployment/tabs/config/Env.tsx +++ b/frontend/src/components/deployment/tabs/config/Env.tsx @@ -6,7 +6,7 @@ import Grid from "../../../util/layout/Grid"; import { useConfig } from "./Provider"; const Env: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); const onAdd = () => { setDeployment("environment", (environment: any) => [ ...environment, @@ -30,14 +30,19 @@ const Env: Component<{}> = (p) => { >
none
- + + + {(_, index) => ( - + = (p) => { value.toUpperCase().replaceAll(" ", "_") ) } + disabled={!userCanUpdate()} /> {" : "} = (p) => { onEdit={(value) => setDeployment("environment", index(), "value", value) } + disabled={!userCanUpdate()} /> - + + + )} diff --git a/frontend/src/components/deployment/tabs/config/Image.tsx b/frontend/src/components/deployment/tabs/config/Image.tsx index 45e212108..a73b2b2c2 100644 --- a/frontend/src/components/deployment/tabs/config/Image.tsx +++ b/frontend/src/components/deployment/tabs/config/Image.tsx @@ -1,34 +1,31 @@ import { Component, Show } from "solid-js"; import { useAppState } from "../../../../state/StateProvider"; -import { useToggle } from "../../../../util/hooks"; +import { useUser } from "../../../../state/UserProvider"; +// import { useToggle } from "../../../../util/hooks"; import Input from "../../../util/Input"; import Flex from "../../../util/layout/Flex"; import Selector from "../../../util/menu/Selector"; import { useConfig } from "./Provider"; const Image: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); const { builds } = useAppState(); - const [show, toggle] = useToggle(); + // const [show, toggle] = useToggle(); return ( - +

{deployment.buildID ? "build" : "image"}

- + - - setDeployment("image", value)} - /> - + setDeployment("image", value)} + disabled={!userCanUpdate()} + /> - + = (p) => { ); }} position="bottom right" + disabled={!userCanUpdate()} /> diff --git a/frontend/src/components/deployment/tabs/config/Network.tsx b/frontend/src/components/deployment/tabs/config/Network.tsx index b99bcd1ee..637660229 100644 --- a/frontend/src/components/deployment/tabs/config/Network.tsx +++ b/frontend/src/components/deployment/tabs/config/Network.tsx @@ -4,12 +4,9 @@ import Selector from "../../../util/menu/Selector"; import { useConfig } from "./Provider"; const Network: Component<{}> = (p) => { - const { deployment, setDeployment, networks } = useConfig(); + const { deployment, setDeployment, networks, userCanUpdate } = useConfig(); return ( - +

network

= (p) => { selected={deployment.network || "bridge"} onSelect={(network) => setDeployment("network", network)} position="bottom right" + disabled={!userCanUpdate()} />
); diff --git a/frontend/src/components/deployment/tabs/config/Ports.tsx b/frontend/src/components/deployment/tabs/config/Ports.tsx index 37526ce62..2ec5fd032 100644 --- a/frontend/src/components/deployment/tabs/config/Ports.tsx +++ b/frontend/src/components/deployment/tabs/config/Ports.tsx @@ -6,7 +6,7 @@ import Grid from "../../../util/layout/Grid"; import { useConfig } from "./Provider"; const Ports: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); const onAdd = () => { setDeployment("ports", (ports: any) => [ ...ports, @@ -24,14 +24,19 @@ const Ports: Component<{}> = (p) => {
none
- + + +
{({ local, container }, index) => ( - + = (p) => { onEdit={(value) => setDeployment("ports", index(), "local", value) } + disabled={!userCanUpdate()} /> {" : "} = (p) => { onEdit={(value) => setDeployment("ports", index(), "container", value) } + disabled={!userCanUpdate()} /> - + + + )} diff --git a/frontend/src/components/deployment/tabs/config/Provider.tsx b/frontend/src/components/deployment/tabs/config/Provider.tsx index eecb30115..a421c09e5 100644 --- a/frontend/src/components/deployment/tabs/config/Provider.tsx +++ b/frontend/src/components/deployment/tabs/config/Provider.tsx @@ -11,6 +11,7 @@ import { import { createStore, DeepReadonly, SetStoreFunction } from "solid-js/store"; import { ADD_UPDATE, UPDATE_DEPLOYMENT } from "../../../../state/actions"; import { useAppState } from "../../../../state/StateProvider"; +import { useUser } from "../../../../state/UserProvider"; import { getDeployment, getNetworks } from "../../../../util/query"; type ConfigDeployment = Deployment & { @@ -26,12 +27,14 @@ type State = { reset: () => void; save: () => void; networks: Accessor; + userCanUpdate: () => boolean; }; const context = createContext(); export const ConfigProvider: Component<{}> = (p) => { const { ws, deployments, selected } = useAppState(); + const { username, permissions } = useUser(); const [editing] = createSignal(false); const [deployment, set] = createStore({ ...deployments.get(selected.id())!, @@ -89,6 +92,16 @@ export const ConfigProvider: Component<{}> = (p) => { onCleanup(unsub); + const userCanUpdate = () => { + if (permissions() > 1) { + return true; + } else if (permissions() > 0 && deployment.owners.includes(username()!)) { + return true; + } else { + return false; + } + }; + const state = { editing, deployment, @@ -96,6 +109,7 @@ export const ConfigProvider: Component<{}> = (p) => { reset: load, save, networks, + userCanUpdate, }; return {p.children}; diff --git a/frontend/src/components/deployment/tabs/config/Restart.tsx b/frontend/src/components/deployment/tabs/config/Restart.tsx index 39e2bc18b..ce4af8813 100644 --- a/frontend/src/components/deployment/tabs/config/Restart.tsx +++ b/frontend/src/components/deployment/tabs/config/Restart.tsx @@ -11,11 +11,12 @@ const RESTART_MODES = [ ]; const Restart: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); return (

restart

= (p) => { ) } position="bottom right" + disabled={!userCanUpdate()} />
); diff --git a/frontend/src/components/deployment/tabs/config/Volumes.tsx b/frontend/src/components/deployment/tabs/config/Volumes.tsx index 2514c0638..9adcf9a6e 100644 --- a/frontend/src/components/deployment/tabs/config/Volumes.tsx +++ b/frontend/src/components/deployment/tabs/config/Volumes.tsx @@ -6,7 +6,7 @@ import Grid from "../../../util/layout/Grid"; import { useConfig } from "./Provider"; const Volumes: Component<{}> = (p) => { - const { deployment, setDeployment } = useConfig(); + const { deployment, setDeployment, userCanUpdate } = useConfig(); const onAdd = () => { setDeployment("volumes", (volumes: any) => [ ...volumes, @@ -14,7 +14,9 @@ const Volumes: Component<{}> = (p) => { ]); }; const onRemove = (index: number) => { - setDeployment("volumes", (volumes) => volumes!.filter((_, i) => i !== index)); + setDeployment("volumes", (volumes) => + volumes!.filter((_, i) => i !== index) + ); }; return ( @@ -24,14 +26,19 @@ const Volumes: Component<{}> = (p) => {
none
- + + +
{({ local, container }, index) => ( - + = (p) => { onEdit={(value) => setDeployment("volumes", index(), "local", value) } + disabled={!userCanUpdate()} /> {" : "} = (p) => { onEdit={(value) => setDeployment("volumes", index(), "container", value) } + disabled={!userCanUpdate()} /> - + + + )} diff --git a/frontend/src/components/login/NotActivated.tsx b/frontend/src/components/login/NotActivated.tsx index 8dfaf6f7a..d2bcb5ad0 100644 --- a/frontend/src/components/login/NotActivated.tsx +++ b/frontend/src/components/login/NotActivated.tsx @@ -3,15 +3,19 @@ import s from "./login.module.scss"; import Grid from "../util/layout/Grid"; import Loading from "../util/loading/Loading"; import { useUser } from "../../state/UserProvider"; +import Flex from "../util/layout/Flex"; const NotActivated: Component<{}> = (p) => { const { logout } = useUser(); return (
- -
account not activated
-
diff --git a/frontend/src/components/users/Users.tsx b/frontend/src/components/users/Users.tsx index 892faf3cb..42fd3f4e6 100644 --- a/frontend/src/components/users/Users.tsx +++ b/frontend/src/components/users/Users.tsx @@ -10,7 +10,9 @@ import { import { USER_UPDATE } from "../../state/actions"; import { useAppState } from "../../state/StateProvider"; import { combineClasses, readablePermissions } from "../../util/helpers"; -import { getUsers, updateUser } from "../../util/query"; +import { deleteUser, getUsers, updateUser } from "../../util/query"; +import ConfirmButton from "../util/ConfirmButton"; +import Icon from "../util/Icon"; import Input from "../util/Input"; import Flex from "../util/layout/Flex"; import Grid from "../util/layout/Grid"; @@ -66,6 +68,12 @@ const Users: Component<{}> = (p) => { > {user.enabled ? "enabled" : "disabled"} + deleteUser(user._id!)} + > + + )} diff --git a/frontend/src/components/util/Input.tsx b/frontend/src/components/util/Input.tsx index 46999af04..2e9988b9c 100644 --- a/frontend/src/components/util/Input.tsx +++ b/frontend/src/components/util/Input.tsx @@ -1,23 +1,29 @@ -import { Component, JSX } from "solid-js"; +import { Component, JSX, Show } from "solid-js"; const Input: Component< { onEdit?: (value: string) => void; onConfirm?: (value: string) => void; onEnter?: (value: string) => void; - } & JSX.InputHTMLAttributes + disabled?: boolean; + } & JSX.InputHTMLAttributes & + JSX.HTMLAttributes > = (p) => { return ( - p.onEdit && p.onEdit(e.currentTarget.value)} - onBlur={(e) => p.onConfirm && p.onConfirm(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - p.onEnter ? p.onEnter(e.currentTarget.value) : e.currentTarget.blur(); - } - }} - /> + {p.value}
}> + p.onEdit && p.onEdit(e.currentTarget.value)} + onBlur={(e) => p.onConfirm && p.onConfirm(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + p.onEnter + ? p.onEnter(e.currentTarget.value) + : e.currentTarget.blur(); + } + }} + /> + ); }; diff --git a/frontend/src/components/util/menu/Selector.tsx b/frontend/src/components/util/menu/Selector.tsx index ec34e5725..8a99e15a7 100644 --- a/frontend/src/components/util/menu/Selector.tsx +++ b/frontend/src/components/util/menu/Selector.tsx @@ -1,4 +1,4 @@ -import { Component, For } from "solid-js"; +import { Component, For, JSX, Show } from "solid-js"; import { useToggle } from "../../../util/hooks"; import Icon from "../Icon"; import { Position } from "./helpers"; @@ -11,37 +11,49 @@ const Selector: Component<{ onSelect?: (item: string, index: number) => void; position?: Position; targetClass?: string; + disabled?: boolean; + disabledClass?: string; + disabledStyle?: JSX.CSSProperties; }> = (p) => { const [show, toggle] = useToggle(); return ( - + {p.selected} - - + } - content={ - - {(item, index) => ( - - )} - - } - position={p.position} - padding="0.25rem" - /> + > + + {p.selected} + + + } + content={ + + {(item, index) => ( + + )} + + } + position={p.position} + padding="0.25rem" + /> + ); }; diff --git a/frontend/src/style/index.scss b/frontend/src/style/index.scss index bde802435..7fbdb0136 100644 --- a/frontend/src/style/index.scss +++ b/frontend/src/style/index.scss @@ -66,6 +66,11 @@ input::placeholder { opacity: 0.6; } +.big-text { + font-size: 1.2rem; + font-weight: 450; +} + .grey-no-hover { background-color: rgba(c.$grey, 0.8); }