forked from github-starred/komodo
userCanUpdate config
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user