userCanUpdate config

This commit is contained in:
mbecker20
2022-04-07 21:00:23 -07:00
parent 0e5c298199
commit cf8ce414ea
15 changed files with 175 additions and 93 deletions
+2
View File
@@ -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 }
+2
View File
@@ -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 }
@@ -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 (
<Show when={dockerAccounts() && dockerAccounts()!.length > 0}>
<Flex
class="config-item shadow"
justifyContent="space-between"
alignItems="center"
>
<h1>docker account</h1>
<Selector
@@ -25,6 +26,7 @@ const DockerAccount: Component<{}> = (p) => {
)
}
position="bottom right"
disabled={!userCanUpdate()}
/>
</Flex>
</Show>
@@ -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) => {
>
<div>none</div>
</Show>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
<Show when={userCanUpdate()}>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
</Show>
</Flex>
</Flex>
<For each={deployment.environment}>
{(_, index) => (
<Flex justifyContent="space-between" alignItems="center">
<Flex
justifyContent={userCanUpdate() ? "space-between" : undefined}
alignItems="center"
>
<Input
placeholder="variable"
value={deployment.environment![index()].variable}
@@ -50,6 +55,7 @@ const Env: Component<{}> = (p) => {
value.toUpperCase().replaceAll(" ", "_")
)
}
disabled={!userCanUpdate()}
/>
{" : "}
<Input
@@ -59,10 +65,13 @@ const Env: Component<{}> = (p) => {
onEdit={(value) =>
setDeployment("environment", index(), "value", value)
}
disabled={!userCanUpdate()}
/>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
<Show when={userCanUpdate()}>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
</Show>
</Flex>
)}
</For>
@@ -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 (
<Flex
class="config-item shadow"
justifyContent="space-between"
>
<Flex class="config-item shadow" justifyContent="space-between">
<h1>{deployment.buildID ? "build" : "image"}</h1>
<Flex>
<Flex alignItems="center">
<Show when={!deployment.buildID}>
<Flex>
<Input
placeholder="image"
spellcheck={false}
value={deployment.image || ""}
style={{ width: "12rem" }}
onEdit={(value) => setDeployment("image", value)}
/>
</Flex>
<Input
placeholder="image"
spellcheck={false}
value={deployment.image || ""}
style={{ width: userCanUpdate() && "12rem" }}
onEdit={(value) => setDeployment("image", value)}
disabled={!userCanUpdate()}
/>
</Show>
<Show when={builds.loaded()}>
<Show when={builds.loaded() && (userCanUpdate() || deployment.buildID)}>
<Selector
targetClass="blue"
selected={
@@ -49,6 +46,7 @@ const Image: Component<{}> = (p) => {
);
}}
position="bottom right"
disabled={!userCanUpdate()}
/>
</Show>
</Flex>
@@ -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 (
<Flex
class="config-item shadow"
justifyContent="space-between"
>
<Flex class="config-item shadow" justifyContent="space-between" alignItems="center">
<h1>network</h1>
<Selector
targetClass="blue"
@@ -17,6 +14,7 @@ const Network: Component<{}> = (p) => {
selected={deployment.network || "bridge"}
onSelect={(network) => setDeployment("network", network)}
position="bottom right"
disabled={!userCanUpdate()}
/>
</Flex>
);
@@ -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) => {
<Show when={!deployment.ports || deployment.ports.length === 0}>
<div>none</div>
</Show>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
<Show when={userCanUpdate()}>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
</Show>
</Flex>
</Flex>
<For each={deployment.ports}>
{({ local, container }, index) => (
<Flex justifyContent="space-between" alignItems="center">
<Flex
justifyContent={userCanUpdate() ? "space-between" : undefined}
alignItems="center"
>
<Input
placeholder="system"
value={local}
@@ -39,6 +44,7 @@ const Ports: Component<{}> = (p) => {
onEdit={(value) =>
setDeployment("ports", index(), "local", value)
}
disabled={!userCanUpdate()}
/>
{" : "}
<Input
@@ -48,10 +54,13 @@ const Ports: Component<{}> = (p) => {
onEdit={(value) =>
setDeployment("ports", index(), "container", value)
}
disabled={!userCanUpdate()}
/>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
<Show when={userCanUpdate()}>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
</Show>
</Flex>
)}
</For>
@@ -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<Network[]>;
userCanUpdate: () => boolean;
};
const context = createContext<State>();
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 <context.Provider value={state}>{p.children}</context.Provider>;
@@ -11,11 +11,12 @@ const RESTART_MODES = [
];
const Restart: Component<{}> = (p) => {
const { deployment, setDeployment } = useConfig();
const { deployment, setDeployment, userCanUpdate } = useConfig();
return (
<Flex
class="config-item shadow"
justifyContent="space-between"
alignItems="center"
>
<h1>restart</h1>
<Selector
@@ -33,6 +34,7 @@ const Restart: Component<{}> = (p) => {
)
}
position="bottom right"
disabled={!userCanUpdate()}
/>
</Flex>
);
@@ -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 (
<Grid class="config-item shadow">
@@ -24,14 +26,19 @@ const Volumes: Component<{}> = (p) => {
<Show when={!deployment.volumes || deployment.volumes.length === 0}>
<div>none</div>
</Show>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
<Show when={userCanUpdate()}>
<button class="green" onClick={onAdd}>
<Icon type="plus" />
</button>
</Show>
</Flex>
</Flex>
<For each={deployment.volumes}>
{({ local, container }, index) => (
<Flex justifyContent="space-between" alignItems="center">
<Flex
justifyContent={userCanUpdate() ? "space-between" : undefined}
alignItems="center"
>
<Input
placeholder="system"
value={local}
@@ -39,6 +46,7 @@ const Volumes: Component<{}> = (p) => {
onEdit={(value) =>
setDeployment("volumes", index(), "local", value)
}
disabled={!userCanUpdate()}
/>
{" : "}
<Input
@@ -48,10 +56,13 @@ const Volumes: Component<{}> = (p) => {
onEdit={(value) =>
setDeployment("volumes", index(), "container", value)
}
disabled={!userCanUpdate()}
/>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
<Show when={userCanUpdate()}>
<button class="red" onClick={() => onRemove(index())}>
<Icon type="minus" />
</button>
</Show>
</Flex>
)}
</For>
@@ -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 (
<div class={s.Login}>
<Grid placeItems="center">
<Loading type="sonar" scale={0.7} />
<div style={{ "font-size": "1.5rem" }}>account not activated</div>
<button class="red" onClick={logout}>
<Flex alignItems="center">
<div style={{ "font-size": "1.5rem" }}>account inactive</div>
<Loading type="sonar" scale={0.7} />
</Flex>
<button class="red big-text" onClick={logout}>
sign out
</button>
</Grid>
+9 -1
View File
@@ -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"}
</button>
<ConfirmButton
color="red"
onConfirm={() => deleteUser(user._id!)}
>
<Icon type="trash" />
</ConfirmButton>
</Flex>
</Flex>
)}
+18 -12
View File
@@ -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<HTMLInputElement>
disabled?: boolean;
} & JSX.InputHTMLAttributes<HTMLInputElement> &
JSX.HTMLAttributes<HTMLDivElement>
> = (p) => {
return (
<input
{...p}
onInput={(e) => 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();
}
}}
/>
<Show when={!p.disabled} fallback={<div {...p}>{p.value}</div>}>
<input
{...p}
onInput={(e) => 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();
}
}}
/>
</Show>
);
};
+39 -27
View File
@@ -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 (
<Menu
show={show()}
close={toggle}
target={
<button class={p.targetClass} onClick={toggle}>
<Show
when={!p.disabled}
fallback={
<div class={p.disabledClass} style={p.disabledStyle}>
{p.selected}
<Icon type="chevron-down" />
</button>
</div>
}
content={
<For each={p.items}>
{(item, index) => (
<button
onClick={() => {
p.onSelect && p.onSelect(item, index());
toggle();
}}
style={{ width: "100%", "justify-content": "flex-end" }}
class={s.SelectorItem}
>
{item}
</button>
)}
</For>
}
position={p.position}
padding="0.25rem"
/>
>
<Menu
show={show()}
close={toggle}
target={
<button class={p.targetClass} onClick={toggle}>
{p.selected}
<Icon type="chevron-down" />
</button>
}
content={
<For each={p.items}>
{(item, index) => (
<button
onClick={() => {
p.onSelect && p.onSelect(item, index());
toggle();
}}
style={{ width: "100%", "justify-content": "flex-end" }}
class={s.SelectorItem}
>
{item}
</button>
)}
</For>
}
position={p.position}
padding="0.25rem"
/>
</Show>
);
};
+5
View File
@@ -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);
}