Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vueify Admin Groups Grid #17126

Merged
merged 8 commits into from
Dec 8, 2023
6 changes: 5 additions & 1 deletion client/src/api/groups.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import axios from "axios";

import { components } from "@/api/schema";
import { components, fetcher } from "@/api/schema";

type GroupModel = components["schemas"]["GroupModel"];
export async function getAllGroups(): Promise<GroupModel[]> {
const { data } = await axios.get("/api/groups");
return data;
}

export const deleteGroup = fetcher.path("/api/groups/{group_id}").method("delete").create();
export const purgeGroup = fetcher.path("/api/groups/{group_id}/purge").method("post").create();
export const undeleteGroup = fetcher.path("/api/groups/{group_id}/undelete").method("post").create();
88 changes: 88 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,12 @@ export interface paths {
get: operations["show_group_api_groups__group_id__get"];
/** Modifies a group. */
put: operations["update_api_groups__group_id__put"];
/** Delete */
delete: operations["delete_api_groups__group_id__delete"];
};
"/api/groups/{group_id}/purge": {
/** Purge */
post: operations["purge_api_groups__group_id__purge_post"];
};
"/api/groups/{group_id}/roles": {
/** Displays a collection (list) of groups. */
Expand All @@ -405,6 +411,10 @@ export interface paths {
/** Removes a role from a group */
delete: operations["delete_api_groups__group_id__roles__role_id__delete"];
};
"/api/groups/{group_id}/undelete": {
/** Undelete */
post: operations["undelete_api_groups__group_id__undelete_post"];
};
"/api/groups/{group_id}/user/{user_id}": {
/**
* Displays information about a group user.
Expand Down Expand Up @@ -12044,6 +12054,58 @@ export interface operations {
};
};
};
delete_api_groups__group_id__delete: {
/** Delete */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
path: {
group_id: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
purge_api_groups__group_id__purge_post: {
/** Purge */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
path: {
group_id: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
group_roles_api_groups__group_id__roles_get: {
/** Displays a collection (list) of groups. */
parameters: {
Expand Down Expand Up @@ -12158,6 +12220,32 @@ export interface operations {
};
};
};
undelete_api_groups__group_id__undelete_post: {
/** Undelete */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
path: {
group_id: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
group_user_api_groups__group_id__user__user_id__get: {
/**
* Displays information about a group user.
Expand Down
13 changes: 13 additions & 0 deletions client/src/components/Grid/GridList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ library.add(faCaretDown, faCaretUp, faShieldAlt);
interface Props {
// provide a grid configuration
gridConfig: GridConfig;
// incoming initial message
gridMessage?: string;
// debounce delay
delay?: number;
// rows per page to be shown
Expand Down Expand Up @@ -81,6 +83,16 @@ function applyFilter(filter: string, value: string | boolean, quoted = false) {
}
}

/**
* Display initial message parsed through route query
*/
function displayInitialMessage() {
if (props.gridMessage) {
operationMessage.value = props.gridMessage;
operationStatus.value = "success";
}
}

/**
* Request grid data
*/
Expand Down Expand Up @@ -164,6 +176,7 @@ function onFilter(filter?: string) {
onMounted(() => {
getGridData();
eventBus.on(onRouterPush);
displayInitialMessage();
});

onUnmounted(() => {
Expand Down
181 changes: 181 additions & 0 deletions client/src/components/Grid/configs/adminGroups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { useEventBus } from "@vueuse/core";
import axios from "axios";

import { deleteGroup, purgeGroup, undeleteGroup } from "@/api/groups";
import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/filtering";
import _l from "@/utils/localization";
import { withPrefix } from "@/utils/redirect";
import { errorMessageAsString } from "@/utils/simple-error";

import type { ActionArray, FieldArray, GridConfig } from "./types";

const { emit } = useEventBus<string>("grid-router-push");

/**
* Local types
*/
type GroupEntry = Record<string, unknown>;

/**
* Request and return data from server
*/
async function getData(offset: number, limit: number, search: string, sort_by: string, sort_desc: boolean) {
const query = {
limit: String(limit),
offset: String(offset),
search: search,
sort_by: sort_by,
sort_desc: String(sort_desc),
};
const queryString = new URLSearchParams(query).toString();
const { data } = await axios.get(withPrefix(`/admin/groups_list?${queryString}`));
return [data.rows, data.rows_total];
}

/**
* Actions are grid-wide operations
*/
const actions: ActionArray = [
{
title: "Create New Group",
icon: faPlus,
handler: () => {
emit("/admin/form/create_group");
},
},
];

/**
* Declare columns to be displayed
*/
const fields: FieldArray = [
{
key: "name",
title: "Name",
type: "operations",
operations: [
{
title: "Edit Name",
icon: faEdit,
condition: (data: GroupEntry) => !data.deleted,
handler: (data: GroupEntry) => {
emit(`/admin/form/rename_group?id=${data.id}`);
},
},
{
title: "Edit Permissions",
icon: faKey,
condition: (data: GroupEntry) => !data.deleted,
handler: (data: GroupEntry) => {
emit(`/admin/form/manage_users_and_roles_for_group?id=${data.id}`);
},
},
{
title: "Delete",
icon: faTrash,
condition: (data: GroupEntry) => !data.deleted,
handler: async (data: GroupEntry) => {
if (confirm(_l("Are you sure that you want to delete this group?"))) {
try {
await deleteGroup({ group_id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been deleted.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to delete '${data.name}': ${errorMessageAsString(e)}`,
};
}
}
},
},
{
title: "Purge",
icon: faTrash,
condition: (data: GroupEntry) => !!data.deleted,
handler: async (data: GroupEntry) => {
if (confirm(_l("Are you sure that you want to purge this group?"))) {
try {
await purgeGroup({ group_id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been purged.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to purge '${data.name}': ${errorMessageAsString(e)}`,
};
}
}
},
},
{
title: "Restore",
icon: faTrashRestore,
condition: (data: GroupEntry) => !!data.deleted,
handler: async (data: GroupEntry) => {
try {
await undeleteGroup({ group_id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been restored.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to restore '${data.name}': ${errorMessageAsString(e)}`,
};
}
},
},
],
},
{
key: "roles",
title: "Roles",
type: "text",
},
{
key: "users",
title: "Users",
type: "text",
},
{
key: "update_time",
title: "Updated",
type: "date",
},
];

const validFilters: Record<string, ValidFilter<string | boolean | undefined>> = {
name: { placeholder: "name", type: String, handler: contains("name"), menuItem: true },
deleted: {
placeholder: "Filter on deleted entries",
type: Boolean,
boolType: "is",
handler: equals("deleted", "deleted", toBool),
menuItem: true,
},
};

/**
* Grid configuration
*/
const gridConfig: GridConfig = {
id: "groups-grid",
actions: actions,
fields: fields,
filtering: new Filtering(validFilters, undefined, false, false),
getData: getData,
plural: "Groups",
sortBy: "name",
sortDesc: true,
sortKeys: ["name", "update_time"],
title: "Groups",
};

export default gridConfig;
Loading