Skip to content

Commit

Permalink
feature: lxd permissions management
Browse files Browse the repository at this point in the history
done:
- identities management
    - Identities list screen
    - Disable selection for tls identities on identities list
    - Identities detail overview
    - Identities detail groups view
    - TLS identities does not have groups view
    - Identities modify groups for a single identity
    - Identities modify groups for multiple identities
    - History undo/redo control for modify groups panel with modified status shown
    - Nested side navigation for storage and permissions
    - Hide nav items when side navigation is collapsed
    - Hover to expand side navigation for medium screen widths
    - Edit identity groups confirm modal
    - Align header layout for storage pages
    - Adjusted navigation to enter "scroll mode" automatically depending on its content overflow
- lxd groups management
    - lxd Groups list view
    - lxd Groups empty state view
    - Setup for group actions menu
    - Create lxd groups in side panel

Signed-off-by: Mason Hu <[email protected]>
  • Loading branch information
mas-who committed May 3, 2024
1 parent 21c466c commit 1c39400
Show file tree
Hide file tree
Showing 102 changed files with 6,816 additions and 362 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@playwright/test": "1.41.0",
"@types/convert-source-map": "2.0.3",
"@types/cytoscape-popper": "2.0.4",
"@types/lodash.isequal": "^4.5.8",
"@types/node-forge": "1.3.11",
"@types/react": "18.2.48",
"@types/react-cytoscapejs": "1.2.5",
Expand Down
3 changes: 3 additions & 0 deletions public/assets/icon/contextual-menu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 41 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ const ProfileList = lazy(() => import("pages/profiles/ProfileList"));
const ProjectConfig = lazy(() => import("pages/projects/ProjectConfiguration"));
const ProtectedRoute = lazy(() => import("components/ProtectedRoute"));
const Settings = lazy(() => import("pages/settings/Settings"));
const Storage = lazy(() => import("pages/storage/Storage"));
const StoragePools = lazy(() => import("pages/storage/StoragePools"));
const StorageVolumes = lazy(() => import("pages/storage/StorageVolumes"));
const CustomIsoList = lazy(() => import("pages/storage/CustomIsoList"));
const StoragePoolDetail = lazy(() => import("pages/storage/StoragePoolDetail"));
const StorageVolumeCreate = lazy(
() => import("pages/storage/forms/StorageVolumeCreate"),
Expand All @@ -52,6 +54,15 @@ const StorageVolumeDetail = lazy(
() => import("pages/storage/StorageVolumeDetail"),
);
const WarningList = lazy(() => import("pages/warnings/WarningList"));
const PermissionIdentities = lazy(
() => import("pages/permissions/PermissionIdentities"),
);
const PermissionGroups = lazy(
() => import("pages/permissions/PermissionGroups"),
);
const PermissionIdpGroups = lazy(
() => import("pages/permissions/PermissionIdpGroups"),
);

const HOME_REDIRECT_PATHS = ["/", "/ui", "/ui/project"];

Expand Down Expand Up @@ -261,23 +272,27 @@ const App: FC = () => {
element={<ProtectedRoute outlet={<CreateProject />} />}
/>
<Route
path="/ui/project/:project/storage"
path="/ui/project/:project/storage/pools"
element={
<ProtectedRoute outlet={<ProjectLoader outlet={<Storage />} />} />
<ProtectedRoute
outlet={<ProjectLoader outlet={<StoragePools />} />}
/>
}
/>
<Route
path="/ui/project/:project/storage/create"
path="/ui/project/:project/storage/pools/create"
element={
<ProtectedRoute
outlet={<ProjectLoader outlet={<CreateStoragePool />} />}
/>
}
/>
<Route
path="/ui/project/:project/storage/:activeTab"
path="/ui/project/:project/storage/volumes"
element={
<ProtectedRoute outlet={<ProjectLoader outlet={<Storage />} />} />
<ProtectedRoute
outlet={<ProjectLoader outlet={<StorageVolumes />} />}
/>
}
/>
<Route
Expand All @@ -288,6 +303,14 @@ const App: FC = () => {
/>
}
/>
<Route
path="/ui/project/:project/storage/custom-isos"
element={
<ProtectedRoute
outlet={<ProjectLoader outlet={<CustomIsoList />} />}
/>
}
/>
<Route
path="/ui/project/:project/storage/pool/:name"
element={
Expand Down Expand Up @@ -352,6 +375,18 @@ const App: FC = () => {
path="/ui/warnings"
element={<ProtectedRoute outlet={<WarningList />} />}
/>
<Route
path="/ui/permissions/identities"
element={<ProtectedRoute outlet={<PermissionIdentities />} />}
/>
<Route
path="/ui/permissions/groups"
element={<ProtectedRoute outlet={<PermissionGroups />} />}
/>
<Route
path="/ui/permissions/idp-groups"
element={<ProtectedRoute outlet={<PermissionIdpGroups />} />}
/>
<Route
path="/ui/settings"
element={<ProtectedRoute outlet={<Settings />} />}
Expand Down
82 changes: 82 additions & 0 deletions src/api/auth-groups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import { LxdApiResponse } from "types/apiResponse";
import { LxdGroup } from "types/permissions";

export const fetchGroups = (): Promise<LxdGroup[]> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups?recursion=1`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdGroup[]>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchGroup = (name: string): Promise<LxdGroup> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups/${name}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdGroup>) => resolve(data.metadata))
.catch(reject);
});
};

export const createGroup = (group: Partial<LxdGroup>): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups`, {
method: "POST",
body: JSON.stringify(group),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const deleteGroup = (group: string): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups/${group}`, {
method: "DELETE",
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const deleteGroups = (groups: string[]): Promise<void> => {
return new Promise((resolve, reject) => {
void Promise.allSettled(groups.map((group) => deleteGroup(group)))
.then(handleSettledResult)
.then(resolve)
.catch(reject);
});
};

export const updateGroup = (group: Partial<LxdGroup>): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups/${group.name}`, {
method: "PUT",
body: JSON.stringify(group),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const renameGroup = (
oldName: string,
newName: string,
): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups/${oldName}`, {
method: "POST",
body: JSON.stringify({
name: newName,
}),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};
52 changes: 52 additions & 0 deletions src/api/auth-identities.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import { LxdApiResponse } from "types/apiResponse";
import { LxdIdentity } from "types/permissions";

export const fetchIdentities = (): Promise<LxdIdentity[]> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identities?recursion=1`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdIdentity[]>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchIdentity = (
id: string,
authMethod: string,
): Promise<LxdIdentity> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identities/${authMethod}/${id}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdIdentity>) => resolve(data.metadata))
.catch(reject);
});
};

export const updateIdentity = (identity: Partial<LxdIdentity>) => {
return new Promise((resolve, reject) => {
fetch(
`/1.0/auth/identities/${identity.authentication_method}/${identity.id}`,
{
method: "PUT",
body: JSON.stringify(identity),
},
)
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const updateIdentities = (
identities: Partial<LxdIdentity>[],
): Promise<void> => {
return new Promise((resolve, reject) => {
void Promise.allSettled(
identities.map((identity) => updateIdentity(identity)),
)
.then(handleSettledResult)
.then(resolve)
.catch(reject);
});
};
88 changes: 88 additions & 0 deletions src/api/auth-idp-groups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import { LxdApiResponse } from "types/apiResponse";
import { IdpGroup } from "types/permissions";

export const fetchIdpGroups = (): Promise<IdpGroup[]> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups?recursion=1`)
.then(handleResponse)
.then((data: LxdApiResponse<IdpGroup[]>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchIdpGroup = (name: string): Promise<IdpGroup> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups/${name}`)
.then(handleResponse)
.then((data: LxdApiResponse<IdpGroup>) => resolve(data.metadata))
.catch(reject);
});
};

export const createIdpGroup = (group: Partial<IdpGroup>): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups`, {
method: "POST",
body: JSON.stringify({ name: group.name }),
})
.then(() =>
fetch(`/1.0/auth/identity-provider-groups/${group.name}`, {
method: "PUT",
body: JSON.stringify(group),
}),
)
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const updateIdpGroup = (group: IdpGroup): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups/${group.name}`, {
method: "PUT",
body: JSON.stringify(group),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const renameIdpGroup = (
oldName: string,
newName: string,
): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups/${oldName}`, {
method: "POST",
body: JSON.stringify({
name: newName,
}),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const deleteIdpGroup = (group: string): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups/${group}`, {
method: "DELETE",
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const deleteIdpGroups = (groups: string[]): Promise<void> => {
return new Promise((resolve, reject) => {
void Promise.allSettled(groups.map((group) => deleteIdpGroup(group)))
.then(handleSettledResult)
.then(resolve)
.catch(reject);
});
};
21 changes: 21 additions & 0 deletions src/api/auth-permissions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { handleResponse } from "util/helpers";
import { LxdApiResponse } from "types/apiResponse";
import { LxdPermission } from "types/permissions";

export const fetchPermissions = (args: {
resourceType: string;
project?: string;
}): Promise<LxdPermission[]> => {
const { resourceType, project } = args;
let url = `/1.0/auth/permissions?entity-type=${resourceType}`;
if (project) {
url += `&project=${project}`;
}

return new Promise((resolve, reject) => {
fetch(url)
.then(handleResponse)
.then((data: LxdApiResponse<LxdPermission[]>) => resolve(data.metadata))
.catch(reject);
});
};
6 changes: 4 additions & 2 deletions src/api/images.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ export const fetchImage = (
});
};

export const fetchImageList = (project: string): Promise<LxdImage[]> => {
export const fetchImageList = (project?: string): Promise<LxdImage[]> => {
const url =
"/1.0/images?recursion=1" + (project ? `&project=${project}` : "");
return new Promise((resolve, reject) => {
fetch(`/1.0/images?recursion=1&project=${project}`)
fetch(url)
.then(handleResponse)
.then((data: LxdApiResponse<LxdImage[]>) => resolve(data.metadata))
.catch(reject);
Expand Down
Loading

0 comments on commit 1c39400

Please sign in to comment.