Skip to content

Commit

Permalink
Update pnpm, delete users from console, refactor actions, and make de…
Browse files Browse the repository at this point in the history
…fault permission "ALLOW_ALL"
  • Loading branch information
pjdotson committed Sep 26, 2024
1 parent 45dff72 commit 2fa1e0a
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 201 deletions.
8 changes: 1 addition & 7 deletions client/ts/src/access/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@ export const actionZ = z.union([
z.literal("copy"),
z.literal("create"),
z.literal("delete"),
z.literal("delete_alias"),
z.literal("delete_key_value"),
z.literal("get_key_value"),
z.literal("list_aliases"),
z.literal("move_children"),
z.literal("remove_children"),
z.literal("remove_label"),
z.literal("rename"),
z.literal("resolve_alias"),
z.literal("resolve"),
z.literal("retrieve"),
z.literal("set_alias"),
z.literal("set_key_value"),
z.literal("update"),
]);
export type Action = z.infer<typeof actionZ>;
Expand Down
8 changes: 1 addition & 7 deletions client/ts/src/ranger/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,4 @@ export const rangeOntologyID = (key: Key): ontology.ID =>
export const rangeAliasOntologyID = (key: Key): ontology.ID =>
new ontology.ID({ type: ALIAS_ONTOLOGY_TYPE, key: key });

export const SET_KV_ACTION: access.Action = "set_key_value";
export const GET_KV_ACTION: access.Action = "get_key_value";
export const DELETE_KV_ACTION: access.Action = "delete_key_value";
export const LIST_ALIASES_ACTION: access.Action = "list_aliases";
export const SET_ALIAS_ACTION: access.Action = "set_alias";
export const DELETE_ALIAS_ACTION: access.Action = "delete_alias";
export const RESOLVE_ALIAS_ACTION: access.Action = "resolve_alias";
export const RESOLVE_ALIAS_ACTION: access.Action = "resolve";
61 changes: 24 additions & 37 deletions console/src/link/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// License, use of this software will be governed by the Apache License, Version 2.0,
// included in the file licenses/APL.txt.

import { Dispatch, UnknownAction } from "@reduxjs/toolkit";
import { ontology, Synnax } from "@synnaxlabs/client";
import { type Dispatch, type UnknownAction } from "@reduxjs/toolkit";
import { type ontology, Synnax, user } from "@synnaxlabs/client";
import { Drift } from "@synnaxlabs/drift";
import { useSelectWindowKey } from "@synnaxlabs/drift/react";
import { Icon } from "@synnaxlabs/media";
Expand All @@ -20,11 +20,13 @@ import {
useSyncedRef,
} from "@synnaxlabs/pluto";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
import { ReactElement } from "react";
import { type ReactElement } from "react";
import { useDispatch, useStore } from "react-redux";

import { Cluster } from "@/cluster";
import { Layout } from "@/layout";
import { Permissions } from "@/permissions";
import { type RootState } from "@/store";

export interface HandlerProps {
addStatus: (status: Status.CrudeSpec) => void;
Expand All @@ -44,64 +46,51 @@ export interface UseDeepProps {

const openUrlErrorMessage =
"Cannot open URL, URLs must be of the form synnax://cluster/<cluster-key> or synnax://cluster/<cluster-key>/<resource>/<resource-key>";
const scheme = "synnax://";

export const useDeep = ({ handlers }: UseDeepProps): void => {
const client = PSynnax.use();
const clientRef = useSyncedRef(client);
const addStatus = Status.useAggregator();
const dispatch = useDispatch();
const placer = Layout.usePlacer();
const store = useStore();
const store = useStore<RootState>();
const windowKey = useSelectWindowKey() as string;
const addOpenUrlErrorStatus = () => {
const addOpenUrlErrorStatus = () =>
addStatus({
variant: "error",
message: openUrlErrorMessage,
});
};

useAsyncEffect(async () => {
const unlisten = await onOpenUrl(async (urls) => {
dispatch(Drift.focusWindow({}));

// Processing URL, making sure is has valid form
const scheme = "synnax://";
if (urls.length === 0 || !urls[0].startsWith(scheme)) {
addOpenUrlErrorStatus();
return;
}
if (urls.length === 0 || !urls[0].startsWith(scheme))
return addOpenUrlErrorStatus();
const urlParts = urls[0].slice(scheme.length).split("/");
if (urlParts.length !== 2 && urlParts.length !== 4) {
addOpenUrlErrorStatus();
return;
}
if (urlParts[0] !== "cluster") {
addOpenUrlErrorStatus();
return;
}
if ((urlParts.length !== 2 && urlParts.length !== 4) || urlParts[0] !== "cluster")
return addOpenUrlErrorStatus();

// Connecting to the cluster
const clusterKey = urlParts[1];
const connParams = Cluster.select(
store.getState() as Cluster.StoreState,
clusterKey,
)?.props;
const addClusterErrorStatus = () => {
const connParams = Cluster.select(store.getState(), clusterKey)?.props;
const addClusterErrorStatus = () =>
addStatus({
variant: "error",
message: `Cannot open URL, Cluster with key ${clusterKey} not found`,
});
};
if (connParams == null) {
addClusterErrorStatus();
return;
}
if (connParams == null) return addClusterErrorStatus();
dispatch(Cluster.setActive(clusterKey));
clientRef.current = new Synnax(connParams);
if (clientRef.current == null) {
addClusterErrorStatus();
return;
}
if (clientRef.current == null) return addClusterErrorStatus();
const username = clientRef.current.props.username;
const user_ = await clientRef.current.user.retrieveByName(username);
const policies = await clientRef.current.access.policy.retrieveFor(
user.ontologyID(user_.key),
);
dispatch(Permissions.set({ policies }));
if (urlParts.length === 2) return;

// Processing the resource part of URL
Expand Down Expand Up @@ -147,14 +136,12 @@ export const useCopyToClipboard = (): ((props: CopyToClipboardProps) => void) =>
return ({ ontologyID, name, clusterKey }) => {
let url = "synnax://cluster/";
const key = clusterKey ?? activeClusterKey;
if (key == null) {
addStatus({
if (key == null)
return addStatus({
variant: "error",
message: `Failed to copy link to ${name} to clipboard`,
description: "No active cluster found",
});
return;
}
url += key;
if (ontologyID != undefined) url += `/${ontologyID.type}/${ontologyID.key}`;
navigator.clipboard.writeText(url).then(
Expand Down
1 change: 1 addition & 0 deletions console/src/ontology/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// included in the file licenses/APL.txt.

export * from "@/ontology/ContextMenu";
export * from "@/ontology/hooks";
export * from "@/ontology/service";
export * from "@/ontology/ServicesProvider";
export * from "@/ontology/Toolbar";
Expand Down
2 changes: 1 addition & 1 deletion console/src/permissions/migrations/v0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export type State = z.infer<typeof stateZ>;

export const ZERO_STATE: State = {
version: "0.0.0",
policies: [],
policies: ALLOW_ALL,
};
60 changes: 46 additions & 14 deletions console/src/user/services/ontology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,90 @@
import { user } from "@synnaxlabs/client";
import { Icon } from "@synnaxlabs/media";
import { Menu as PMenu, Tree } from "@synnaxlabs/pluto";
import { errors } from "@synnaxlabs/x";
import { useMutation } from "@tanstack/react-query";
import { type ReactElement } from "react";

import { Menu } from "@/components/menu";
import { Ontology } from "@/ontology";
import { Permissions } from "@/permissions";
import { useSelectHasPermission } from "@/user/selectors";

const useSetPermissions =
const useEditPermissions =
(): ((props: Ontology.TreeContextMenuProps) => void) =>
({ placeLayout, selection }) =>
placeLayout(
Permissions.editLayout({ user: selection.resources[0].data as user.User }),
);

const useDelete = (): ((props: Ontology.TreeContextMenuProps) => void) => {
const confirm = Ontology.useConfirmDelete({ type: user.ONTOLOGY_TYPE });
return useMutation<void, Error, Ontology.TreeContextMenuProps, Tree.Node[]>({
onMutate: async ({ state: { nodes, setNodes }, selection: { resources } }) => {
if (!(await confirm(resources))) throw errors.CANCELED;
const prevNodes = Tree.deepCopy(nodes);
setNodes([
...Tree.removeNode({
tree: nodes,
keys: resources.map(({ id }) => id.toString()),
}),
]);
return prevNodes;
},
mutationFn: async ({ selection: { resources }, client }) =>
await client.user.delete(resources.map(({ id }) => id.key)),
onError: (e, { addStatus, state: { setNodes } }, prevNodes) => {
if (prevNodes != null) setNodes(prevNodes);
if (errors.CANCELED.matches(e)) return;
addStatus({
variant: "error",
message: "Failed to delete users.",
description: e.message,
});
},
}).mutate;
};

const TreeContextMenu: Ontology.TreeContextMenu = (props): ReactElement => {
const {
client,
selection: { nodes, resources },
} = props;
const setPermissions = useSetPermissions();
const editPermissions = useEditPermissions();
const handleDelete = useDelete();
const handleSelect = {
permissions: () => setPermissions(props),
permissions: () => editPermissions(props),
rename: () => Tree.startRenaming(nodes[0].key),
delete: () => handleDelete(props),
};
const singleResource = resources.length === 1;
const isNotCurrentUser = resources[0].name !== client.props.username;
const canSetPermissions = Permissions.useSelectCanEditPolicies();
const canEdit = useSelectHasPermission();
const canEditPermissions = Permissions.useSelectCanEditPolicies();
const canEditOrDelete = useSelectHasPermission();

return (
<PMenu.Menu onChange={handleSelect} level="small" iconSpacing="small">
{singleResource && isNotCurrentUser && (
<>
{canSetPermissions && (
{canEditPermissions && (
<PMenu.Item itemKey="permissions" startIcon={<Icon.Access />}>
Edit Permissions
</PMenu.Item>
)}
{canEdit && (
{canEditOrDelete && (
<PMenu.Item itemKey="rename" startIcon={<Icon.Rename />}>
Change Username
</PMenu.Item>
)}
<PMenu.Divider />
</>
)}
{canEditOrDelete && (
<>
<Menu.DeleteItem />
<PMenu.Divider />
</>
)}
<Menu.HardReloadItem />
</PMenu.Menu>
);
Expand All @@ -66,17 +104,11 @@ const handleRename: Ontology.HandleTreeRename = {
await client.user.changeUsername(id.key, name),
};

const allowRename: Ontology.AllowRename = (props): boolean => {
console.log("allowRename");
console.log(props);
return true;
};

export const ONTOLOGY_SERVICE: Ontology.Service = {
type: user.ONTOLOGY_TYPE,
icon: <Icon.User />,
hasChildren: true,
allowRename,
allowRename: () => true,
onRename: handleRename,
haulItems: () => [],
canDrop: () => false,
Expand Down
Loading

0 comments on commit 2fa1e0a

Please sign in to comment.