handle permissions

This commit is contained in:
mbecker20
2023-01-18 06:22:13 +00:00
parent f96ee3f1e8
commit 3648a6efd4
21 changed files with 207 additions and 186 deletions
@@ -32,9 +32,9 @@ const PERMISSIONS_OPTIONS = [
PermissionLevel.Update,
];
const Owners: Component<{}> = (p) => {
const Permissions: Component<{}> = (p) => {
const { ws } = useAppState();
const { build } = useConfig();
const { build, reset } = useConfig();
const { user } = useUser();
const params = useParams();
const [userSearch, setUserSearch] = createSignal("");
@@ -54,17 +54,22 @@ const Owners: Component<{}> = (p) => {
build.permissions![getId(u)] === PermissionLevel.None)
)
);
let unsub = () => {};
let unsub_permissions = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe(
[Operation.ModifyUserPermissions, Operation.ModifyUserEnabled],
() => {
client.list_users().then(setUsers);
}
unsub_permissions();
unsub_permissions = ws.subscribe([Operation.ModifyUserPermissions], () =>
reset()
);
});
onCleanup(() => unsub());
onCleanup(() => unsub_permissions());
let unsub_enabled = () => {};
createEffect(() => {
unsub_enabled();
unsub_enabled = ws.subscribe([Operation.ModifyUserEnabled], () =>
client.list_users().then(setUsers)
);
});
onCleanup(() => unsub_enabled());
return (
<Show when={build.loaded}>
<Grid class="config">
@@ -137,34 +142,36 @@ const Owners: Component<{}> = (p) => {
{u().username}
{user_id === getId(user()) && " ( you )"}
</div>
<Flex alignItems="center">
<Selector
selected={permissions()}
items={PERMISSIONS_OPTIONS}
onSelect={(permission) => {
client.update_user_permissions_on_target({
user_id,
permission: permission as PermissionLevel,
target_type: PermissionsTarget.Build,
target_id: params.id,
});
}}
position="bottom right"
/>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_user_permissions_on_target({
user_id,
permission: PermissionLevel.None,
target_type: PermissionsTarget.Build,
target_id: params.id,
});
}}
>
remove
</ConfirmButton>
</Flex>
<Show when={!u().admin && user_id !== getId(user())}>
<Flex alignItems="center">
<Selector
selected={permissions()}
items={PERMISSIONS_OPTIONS}
onSelect={(permission) => {
client.update_user_permissions_on_target({
user_id,
permission: permission as PermissionLevel,
target_type: PermissionsTarget.Build,
target_id: params.id,
});
}}
position="bottom right"
/>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_user_permissions_on_target({
user_id,
permission: PermissionLevel.None,
target_type: PermissionsTarget.Build,
target_id: params.id,
});
}}
>
remove
</ConfirmButton>
</Flex>
</Show>
</Flex>
</Show>
);
@@ -177,4 +184,4 @@ const Owners: Component<{}> = (p) => {
);
};
export default Owners;
export default Permissions;
+1 -1
View File
@@ -8,7 +8,7 @@ import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { Tab } from "../../shared/tabs/Tabs";
import BuildConfig from "./build-config/BuildConfig";
import GitConfig from "./git-config/GitConfig";
import Owners from "./Owners";
import Owners from "./Permissions";
import { ConfigProvider } from "./Provider";
const BuildTabs: Component<{}> = (p) => {
@@ -17,7 +17,7 @@ import Updates from "./Updates";
const Deployment2: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const { isSemiMobile, isMobile } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
@@ -42,7 +42,7 @@ const Deployment2: Component<{}> = (p) => {
<Header />
<Actions />
</Grid>
<Show when={!isMobile()}>
<Show when={!isSemiMobile()}>
<Updates />
</Show>
</Grid>
@@ -38,11 +38,11 @@ const Header: Component<{}> = (p) => {
class={combineClasses("card shadow")}
style={{
position: "relative",
cursor: isSemiMobile() && userCanUpdate() ? "pointer" : undefined,
cursor: isSemiMobile() ? "pointer" : undefined,
height: "fit-content",
}}
onClick={() => {
if (isSemiMobile() && userCanUpdate()) toggleShowUpdates();
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Flex alignItems="center" justifyContent="space-between">
@@ -8,23 +8,23 @@ import {
onCleanup,
Show,
} from "solid-js";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { client } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import {
Operation,
PermissionLevel,
PermissionsTarget,
User,
} from "../../../../types";
import { combineClasses, getId } from "../../../../util/helpers";
import ConfirmButton from "../../../shared/ConfirmButton";
import Input from "../../../shared/Input";
import Flex from "../../../shared/layout/Flex";
import Grid from "../../../shared/layout/Grid";
import Menu from "../../../shared/menu/Menu";
import Selector from "../../../shared/menu/Selector";
import { useConfig } from "./Provider";
} from "../../../types";
import { combineClasses, getId } from "../../../util/helpers";
import ConfirmButton from "../../shared/ConfirmButton";
import Input from "../../shared/Input";
import Flex from "../../shared/layout/Flex";
import Grid from "../../shared/layout/Grid";
import Menu from "../../shared/menu/Menu";
import Selector from "../../shared/menu/Selector";
import { useConfig } from "./config/Provider";
const PERMISSIONS_OPTIONS = [
PermissionLevel.Read,
@@ -34,7 +34,7 @@ const PERMISSIONS_OPTIONS = [
const Permissions: Component<{}> = (p) => {
const { ws } = useAppState();
const { deployment } = useConfig();
const { deployment, reset } = useConfig();
const { user } = useUser();
const params = useParams();
const [userSearch, setUserSearch] = createSignal("");
@@ -54,17 +54,22 @@ const Permissions: Component<{}> = (p) => {
deployment.permissions![getId(u)] === PermissionLevel.None)
)
);
let unsub = () => {};
let unsub_permissions = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe(
[Operation.ModifyUserPermissions, Operation.ModifyUserEnabled],
() => {
client.list_users().then(setUsers);
}
unsub_permissions();
unsub_permissions = ws.subscribe([Operation.ModifyUserPermissions], () =>
reset()
);
});
onCleanup(() => unsub());
onCleanup(() => unsub_permissions());
let unsub_enabled = () => {};
createEffect(() => {
unsub_enabled();
unsub_enabled = ws.subscribe([Operation.ModifyUserEnabled], () =>
client.list_users().then(setUsers)
);
});
onCleanup(() => unsub_enabled());
return (
<Grid class="config">
<Grid
@@ -21,7 +21,7 @@ import {
import { client } from "../../..";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { ConfigProvider } from "./config/Provider";
import Permissions from "./config/Permissions";
import Permissions from "./Permissions";
import { useUser } from "../../../state/UserProvider";
const DeploymentTabs: Component<{}> = () => {
@@ -17,7 +17,7 @@ import { Tab } from "../../../shared/tabs/Tabs";
import RepoMount from "./mount-repo/RepoMount";
import { OnClone, OnPull } from "./mount-repo/OnGit";
import Loading from "../../../shared/loading/Loading";
import Permissions from "./Permissions";
import Permissions from "../Permissions";
import { pushNotification, URL } from "../../../..";
import { combineClasses, copyToClipboard, getId } from "../../../../util/helpers";
import { useAppDimensions } from "../../../../state/DimensionProvider";
@@ -1,26 +1,16 @@
import { A } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { PermissionLevel } from "../../../types";
import {
combineClasses,
getId,
readableMonitorTimestamp,
} from "../../../util/helpers";
import Icon from "../../shared/Icon";
import Flex from "../../shared/layout/Flex";
import HoverMenu from "../../shared/menu/HoverMenu";
import s from "../home.module.scss";
const Build: Component<{ id: string }> = (p) => {
const { builds } = useAppState();
const { user } = useUser();
const build = () => builds.get(p.id)!;
const permissionLevel = () => {
const level = build().permissions![getId(user())];
return level ? level : PermissionLevel.None;
};
const version = () => {
return `v${build().version.major}.${build().version.minor}.${
build().version.patch
@@ -42,16 +32,6 @@ const Build: Component<{ id: string }> = (p) => {
<A href={`/build/${p.id}`} class={combineClasses(s.DropdownItem)}>
<h2>{build().name}</h2>
<Flex>
<Show
when={!user().admin && permissionLevel() !== PermissionLevel.None}
>
<HoverMenu
target={<Icon type="edit" style={{ padding: "0.25rem" }} />}
content="you are a collaborator"
padding="0.5rem"
position="bottom right"
/>
</Show>
<div>{version()}</div>
<div style={{ opacity: 0.7 }}>{lastBuiltAt()}</div>
</Flex>
@@ -1,23 +1,14 @@
import { A } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { PermissionLevel } from "../../../types";
import { combineClasses, deploymentStateClass, getId } from "../../../util/helpers";
import Circle from "../../shared/Circle";
import Icon from "../../shared/Icon";
import Flex from "../../shared/layout/Flex";
import HoverMenu from "../../shared/menu/HoverMenu";
import s from "../home.module.scss";
const Deployment: Component<{ id: string }> = (p) => {
const { deployments } = useAppState();
const { user } = useUser();
const deployment = () => deployments.get(p.id)!;
const permissionLevel = () => {
const level = deployment().deployment.permissions![getId(user())];
return level ? level : PermissionLevel.None;
};
return (
<Show when={deployment()}>
<A
@@ -28,18 +19,6 @@ const Deployment: Component<{ id: string }> = (p) => {
>
<h2>{deployment().deployment.name}</h2>
<Flex alignItems="center">
<Show
when={
!user().admin && permissionLevel() !== PermissionLevel.None
}
>
<HoverMenu
target={<Icon type="edit" style={{ padding: "0.25rem" }} />}
content="you are a collaborator"
padding="0.5rem"
position="bottom center"
/>
</Show>
<div style={{ opacity: 0.7 }}>{deployments.status(p.id)}</div>
<Circle
size={1}
@@ -32,9 +32,9 @@ const PERMISSIONS_OPTIONS = [
PermissionLevel.Update,
];
const Owners: Component<{}> = (p) => {
const Permissions: Component<{}> = (p) => {
const { ws } = useAppState();
const { server } = useConfig();
const { server, reset } = useConfig();
const { user } = useUser();
const params = useParams();
const [userSearch, setUserSearch] = createSignal("");
@@ -54,21 +54,29 @@ const Owners: Component<{}> = (p) => {
server.permissions![getId(u)] === PermissionLevel.None)
)
);
let unsub = () => {};
let unsub_permissions = () => {};
createEffect(() => {
unsub();
unsub = ws.subscribe(
[Operation.ModifyUserPermissions, Operation.ModifyUserEnabled],
() => {
client.list_users().then(setUsers);
}
unsub_permissions();
unsub_permissions = ws.subscribe([Operation.ModifyUserPermissions], () =>
reset()
);
});
onCleanup(() => unsub());
onCleanup(() => unsub_permissions());
let unsub_enabled = () => {};
createEffect(() => {
unsub_enabled();
unsub_enabled = ws.subscribe([Operation.ModifyUserEnabled], () =>
client.list_users().then(setUsers)
);
});
onCleanup(() => unsub_enabled());
return (
<Show when={server.loaded}>
<Grid class="config">
<Grid class="config-items scroller" style={{ height: "100%", "min-height": "400px" }}>
<Grid
class="config-items scroller"
style={{ height: "100%", "min-height": "400px" }}
>
<Grid class={combineClasses("config-item shadow")} gap="0.5rem">
<Menu
show={userSearch() ? true : false}
@@ -134,34 +142,36 @@ const Owners: Component<{}> = (p) => {
{u().username}
{user_id === getId(user()) && " ( you )"}
</div>
<Flex alignItems="center">
<Selector
selected={permissions()}
items={PERMISSIONS_OPTIONS}
onSelect={(permission) => {
client.update_user_permissions_on_target({
user_id,
permission: permission as PermissionLevel,
target_type: PermissionsTarget.Server,
target_id: params.id,
});
}}
position="bottom right"
/>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_user_permissions_on_target({
user_id,
permission: PermissionLevel.None,
target_type: PermissionsTarget.Server,
target_id: params.id,
});
}}
>
remove
</ConfirmButton>
</Flex>
<Show when={!u().admin && user_id !== getId(user())}>
<Flex alignItems="center">
<Selector
selected={permissions()}
items={PERMISSIONS_OPTIONS}
onSelect={(permission) => {
client.update_user_permissions_on_target({
user_id,
permission: permission as PermissionLevel,
target_type: PermissionsTarget.Server,
target_id: params.id,
});
}}
position="bottom right"
/>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_user_permissions_on_target({
user_id,
permission: PermissionLevel.None,
target_type: PermissionsTarget.Server,
target_id: params.id,
});
}}
>
remove
</ConfirmButton>
</Flex>
</Show>
</Flex>
</Show>
);
@@ -174,4 +184,4 @@ const Owners: Component<{}> = (p) => {
);
};
export default Owners;
export default Permissions;
+4 -4
View File
@@ -6,7 +6,7 @@ import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { Tab } from "../../shared/tabs/Tabs";
import Config from "./config/Config";
import { ConfigProvider } from "./config/Provider";
import Owners from "./Owners";
import Permissions from "./Permissions";
const ServerTabs: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -18,7 +18,7 @@ const ServerTabs: Component<{}> = (p) => {
<ConfigProvider>
<SimpleTabs
containerClass="card shadow"
containerStyle={{ "width": "100%", "box-sizing": "border-box" }}
containerStyle={{ width: "100%", "box-sizing": "border-box" }}
tabs={
[
{
@@ -26,8 +26,8 @@ const ServerTabs: Component<{}> = (p) => {
element: () => <Config />,
},
user().admin && {
title: "collaborators",
element: () => <Owners />,
title: "permissions",
element: () => <Permissions />,
},
].filter((e) => e) as Tab[]
}
@@ -5,17 +5,19 @@ import Flex from "../../../shared/layout/Flex";
import { useConfig } from "./Provider";
const Address: Component<{}> = (p) => {
const { server, setServer } = useConfig();
const { server, setServer, userCanUpdate } = useConfig();
return (
<Flex
class={combineClasses("config-item shadow")}
justifyContent="space-between"
alignItems="center"
>
<h1>address</h1>
<Input
value={server.address}
placeholder="address"
onEdit={(value) => setServer("address", value)}
disabled={!userCanUpdate()}
/>
</Flex>
);
@@ -7,7 +7,7 @@ import Grid from "../../../shared/layout/Grid";
import { useConfig } from "./Provider";
const Alerts: Component<{}> = (p) => {
const { server, setServer } = useConfig();
const { server, setServer, userCanUpdate } = useConfig();
const [alerts, setAlerts] = createStore({
cpu: server.cpu_alert?.toString(),
mem: server.mem_alert?.toString(),
@@ -32,6 +32,7 @@ const Alerts: Component<{}> = (p) => {
}
}}
style={{ width: "4rem" }}
disabled={!userCanUpdate()}
/>
<div>%</div>
</Flex>
@@ -51,6 +52,7 @@ const Alerts: Component<{}> = (p) => {
}
}}
style={{ width: "4rem" }}
disabled={!userCanUpdate()}
/>
<div>%</div>
</Flex>
@@ -70,6 +72,7 @@ const Alerts: Component<{}> = (p) => {
}
}}
style={{ width: "4rem" }}
disabled={!userCanUpdate()}
/>
<div>%</div>
</Flex>
@@ -19,7 +19,7 @@ const Config: Component<{}> = (p) => {
<Grid class="config-items scroller">
<Address />
<Enabled />
<Networks />
{/* <Networks /> */}
<Info />
<Alerts />
<ToNotify />
@@ -4,7 +4,7 @@ import Flex from "../../../shared/layout/Flex";
import { useConfig } from "./Provider";
const Enabled: Component<{}> = (p) => {
const { server, setServer } = useConfig();
const { server, setServer, userCanUpdate } = useConfig();
return (
<Flex
class={combineClasses("config-item shadow")}
@@ -15,6 +15,7 @@ const Enabled: Component<{}> = (p) => {
<button
class={server.enabled ? "green" : "red"}
onClick={() => setServer("enabled", !server.enabled)}
disabled={!userCanUpdate()}
>
{server.enabled ? "yes" : "no"}
</button>
@@ -6,7 +6,7 @@ import Grid from "../../../shared/layout/Grid";
import { useConfig } from "./Provider";
const Info: Component<{}> = (p) => {
const { server, setServer } = useConfig();
const { server, setServer, userCanUpdate } = useConfig();
return (
<Grid class={combineClasses("config-item shadow")}>
<h1>info</h1>
@@ -15,6 +15,7 @@ const Info: Component<{}> = (p) => {
<Input
value={server.region}
onEdit={(value) => setServer("region", value)}
disabled={!userCanUpdate()}
/>
</Flex>
{/* <Flex justifyContent="space-between" alignItems="center">
@@ -1,5 +1,5 @@
import { Component, createSignal, For } from "solid-js";
import { pushNotification } from "../../../..";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import ConfirmButton from "../../../shared/ConfirmButton";
import Icon from "../../../shared/Icon";
@@ -7,17 +7,23 @@ import { useConfig } from "./Provider";
import { combineClasses } from "../../../../util/helpers";
const ToNotify: Component<{}> = (p) => {
const { server, setServer } = useConfig();
const { server, setServer, userCanUpdate } = useConfig();
return (
<Grid class={combineClasses("config-item shadow")}>
<Flex alignItems="center" justifyContent="space-between">
<h1>notify</h1>
<button
class="green"
onClick={() => setServer("to_notify", (toNotify) => toNotify ? [...toNotify, ""] : [""])}
>
<Icon type="plus" />
</button>
<Show when={userCanUpdate()}>
<button
class="green"
onClick={() =>
setServer("to_notify", (toNotify) =>
toNotify ? [...toNotify, ""] : [""]
)
}
>
<Icon type="plus" />
</button>
</Show>
</Flex>
<For each={server.to_notify}>
{(user, index) => (
@@ -26,23 +32,26 @@ const ToNotify: Component<{}> = (p) => {
placeholder="slack user id"
value={user}
onEdit={(user) => setServer("to_notify", index(), user)}
disabled={!userCanUpdate()}
/>
<button
class="red"
onClick={() =>
setServer("to_notify", (toNotify) =>
toNotify!.filter((_, i) => i !== index())
)
}
>
<Icon type="trash" />
</button>
<Show when={userCanUpdate()}>
<button
class="red"
onClick={() =>
setServer("to_notify", (toNotify) =>
toNotify!.filter((_, i) => i !== index())
)
}
>
<Icon type="trash" />
</button>
</Show>
</Flex>
)}
</For>
<Show when={server.to_notify?.length === 0}>
<div>no slack users to notify</div>
</Show>
<Show when={server.to_notify?.length === 0}>
<div>no slack users to notify</div>
</Show>
</Grid>
);
};
@@ -74,16 +74,16 @@ export const Search: Component<{}> = (p) => {
const SearchMenu: Component<{ close: () => void }> = (p) => {
const { tab, input, search } = useSearchState();
const { isMobile } = useAppDimensions();
const { isSemiMobile } = useAppDimensions();
let inputRef: HTMLInputElement | undefined;
onMount(() => {
if (isMobile()) {
if (isSemiMobile()) {
inputRef?.focus();
}
});
return (
<>
<Show when={isMobile()}>
<Show when={isSemiMobile()}>
<Input
ref={() => inputRef}
class={s.SearchInput}
@@ -91,13 +91,13 @@ const SearchMenu: Component<{ close: () => void }> = (p) => {
value={search.value()}
onEdit={input.onEdit}
onKeyDown={input.onKeyDown(inputRef)}
style={{ width: isMobile() ? "100%" : undefined }}
style={{ width: isSemiMobile() ? "100%" : undefined }}
/>
</Show>
<ControlledTabs
selected={tab.selected}
set={tab.set}
containerStyle={{ width: isMobile() ? "100%" : "30rem", gap: "0.5rem" }}
containerStyle={{ width: isSemiMobile() ? "100%" : "30rem", gap: "0.5rem" }}
tabs={[
{
title: "deployments",
+5 -3
View File
@@ -211,7 +211,7 @@ export function useCollection<T>(
query: () => Promise<Collection<T>>,
idPath: string[]
) {
const [collection, { mutate }] = createResource(query);
const [collection, { mutate, refetch }] = createResource(query);
const add = (item: T) => {
mutate((collection: any) => ({
...collection,
@@ -225,10 +225,11 @@ export function useCollection<T>(
mutate((collection: any) => filterOutFromObj(collection, [id]));
};
const update = (item: T) => {
const id = getNestedEntry(item, idPath);
mutate((collection: any) => ({
...collection,
[getNestedEntry(item, idPath)]: {
...collection[getNestedEntry(item, idPath)],
[id]: {
...collection[id],
...item,
},
}));
@@ -259,5 +260,6 @@ export function useCollection<T>(
loaded,
filter,
filterArray,
refetch,
};
}
+22
View File
@@ -183,6 +183,28 @@ async function handleMessage(
procedures.update(procedure);
}
}
// permissions
else if (update.operation === Operation.ModifyUserPermissions) {
if (update.status === UpdateStatus.Complete) {
if (update.target.type === "Build") {
const build = await client.get_build(update.target.id);
builds.update(build);
} else if (update.target.type === "Deployment") {
const deployment = await client.get_deployment(update.target.id);
deployments.update(deployment);
} else if (update.target.type === "Server") {
const server = await client.get_server(update.target.id);
servers.update(server);
} else if (update.target.type === "Group") {
const group = await client.get_group(update.target.id);
groups.update(group);
} else if (update.target.type === "Procedure") {
const procedure = await client.get_procedure(update.target.id);
procedures.update(procedure);
}
}
}
}
export default connectToWs;