diff --git a/src/api/fetchers.ts b/src/api/fetchers.ts index 061fcfeb..ce0296c7 100644 --- a/src/api/fetchers.ts +++ b/src/api/fetchers.ts @@ -652,11 +652,9 @@ export async function logout() { // cross-origin URL the dev proxy can't follow, so the browser reports a // network error even though we're logged out. Handle it in LogoutPage // instead of surfacing the global connection-error modal. - const res = await axios.post( - `${BASE_URL}/loginManager/logout`, - undefined, - { skipErrorNotifications: true } as CustomAxiosRequestConfig - ); + const res = await axios.post(`${BASE_URL}/loginManager/logout`, undefined, { + skipErrorNotifications: true, + } as CustomAxiosRequestConfig); return res.data; } @@ -1509,3 +1507,101 @@ export async function updateDrawerGroup( export async function deleteDrawerGroup(groupId: number): Promise { await axios.delete(`${BASE_URL}/drawerPermissions/groups/${groupId}`); } + +// The drawer-manager twin of fetchUserAutocomplete: same shape, but the +// endpoint is open to any drawer manager, not just instance admins. +export async function fetchDrawerUserAutocomplete( + query: string, + options?: { signal?: AbortSignal } +): Promise { + const res = await axios.get<{ matches: UserAutocompleteMatch[] }>( + `${BASE_URL}/drawerPermissions/userAutocomplete`, + { params: { q: query }, signal: options?.signal } + ); + + return res.data.matches; +} + +export async function fetchDrawerGroupMembers( + groupId: number +): Promise { + const res = await axios.get<{ members: GroupMember[] }>( + `${BASE_URL}/drawerPermissions/groups/${groupId}/members` + ); + + return res.data.members; +} + +export async function addDrawerGroupMember( + input: AddGroupMemberInput +): Promise { + const params = new URLSearchParams(); + if ("localUserId" in input) { + params.append("localUserId", String(input.localUserId)); + } else { + params.append("remoteUserId", input.remoteUserId); + } + + const res = await axios.post<{ member: GroupMember }>( + `${BASE_URL}/drawerPermissions/groups/${input.groupId}/members`, + params + ); + + return res.data.member; +} + +export async function removeDrawerGroupMember( + groupId: number, + userId: number +): Promise { + await axios.delete( + `${BASE_URL}/drawerPermissions/groups/${groupId}/members/${userId}` + ); +} + +export async function fetchDrawerGroupEntries( + groupId: PermissionsGroup["id"] +): Promise { + const res = await axios.get<{ entries: PermissionsGroupEntry[] }>( + `${BASE_URL}/drawerPermissions/groups/${groupId}/entries` + ); + return res.data.entries; +} + +export async function addDrawerGroupEntry({ + groupId, + value, +}: AddGroupEntryInput): Promise { + const payload = new URLSearchParams({ value }); + + const res = await axios.post<{ entry: PermissionsGroupEntry }>( + `${BASE_URL}/drawerPermissions/groups/${groupId}/entries`, + payload + ); + + return res.data.entry; +} + +export async function updateDrawerGroupEntry({ + groupId, + entryId, + value, +}: UpdateGroupEntryInput): Promise { + const payload = new URLSearchParams({ value }); + + const res = await axios.put<{ entry: PermissionsGroupEntry }>( + `${BASE_URL}/drawerPermissions/groups/${groupId}/entries/${entryId}`, + payload + ); + + return res.data.entry; +} + +export async function removeDrawerGroupEntry( + groupId: PermissionsGroup["id"], + entryId: PermissionsGroupEntry["id"] +): Promise { + await axios.delete( + `${BASE_URL}/drawerPermissions/groups/${groupId}/entries/${entryId}` + ); +} diff --git a/src/pages/AdminPermissionsPage/GroupsTabContent.vue b/src/pages/AdminPermissionsPage/GroupsTabContent.vue index 3b2782d3..2a030308 100644 --- a/src/pages/AdminPermissionsPage/GroupsTabContent.vue +++ b/src/pages/AdminPermissionsPage/GroupsTabContent.vue @@ -34,9 +34,12 @@ v-for="header in headerGroup.headers" :key="header.id" class="bg-surface-container-low" - :class="{ - 'cursor-pointer select-none': header.column.getCanSort(), - }" + :class="[ + header.column.columnDef.meta?.widthClass, + { + 'cursor-pointer select-none': header.column.getCanSort(), + }, + ]" @click="header.column.getToggleSortingHandler()?.($event)">
+ + +

{{ pendingValue }} (saving...)

+
+ + + + +
+
+ + + diff --git a/src/pages/DrawerManagementPage/AddGroupMemberRow.vue b/src/pages/DrawerManagementPage/AddGroupMemberRow.vue new file mode 100644 index 00000000..7b89ed93 --- /dev/null +++ b/src/pages/DrawerManagementPage/AddGroupMemberRow.vue @@ -0,0 +1,262 @@ + + + diff --git a/src/pages/DrawerManagementPage/DrawerManagementPage.vue b/src/pages/DrawerManagementPage/DrawerManagementPage.vue index 5a1ce000..b5b0d787 100644 --- a/src/pages/DrawerManagementPage/DrawerManagementPage.vue +++ b/src/pages/DrawerManagementPage/DrawerManagementPage.vue @@ -8,9 +8,6 @@ - - - @@ -30,11 +27,10 @@ import Tabs from "@/components/Tabs/Tabs.vue"; import Tab from "@/components/Tabs/Tab.vue"; import { computed } from "vue"; import { useRoute, useRouter } from "vue-router"; -import DrawersTabContent from "./DrawersTabContent.vue"; import RulesTabContent from "./RulesTabContent.vue"; import GroupsTabContent from "./GroupsTabContent.vue"; -const VALID_TABS = ["drawers", "rules", "groups"] as const; +const VALID_TABS = ["rules", "groups"] as const; type ValidTab = (typeof VALID_TABS)[number]; const route = useRoute(); diff --git a/src/pages/DrawerManagementPage/GroupEntriesManager.vue b/src/pages/DrawerManagementPage/GroupEntriesManager.vue new file mode 100644 index 00000000..09724f2b --- /dev/null +++ b/src/pages/DrawerManagementPage/GroupEntriesManager.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/pages/DrawerManagementPage/GroupEntriesTable.vue b/src/pages/DrawerManagementPage/GroupEntriesTable.vue new file mode 100644 index 00000000..ee36887e --- /dev/null +++ b/src/pages/DrawerManagementPage/GroupEntriesTable.vue @@ -0,0 +1,66 @@ + + diff --git a/src/pages/DrawerManagementPage/GroupEntriesTableRow.vue b/src/pages/DrawerManagementPage/GroupEntriesTableRow.vue new file mode 100644 index 00000000..64c6c790 --- /dev/null +++ b/src/pages/DrawerManagementPage/GroupEntriesTableRow.vue @@ -0,0 +1,143 @@ + + + diff --git a/src/pages/DrawerManagementPage/GroupEntryValueInput.vue b/src/pages/DrawerManagementPage/GroupEntryValueInput.vue new file mode 100644 index 00000000..21cd6c5a --- /dev/null +++ b/src/pages/DrawerManagementPage/GroupEntryValueInput.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/pages/DrawerManagementPage/GroupMemberManager.vue b/src/pages/DrawerManagementPage/GroupMemberManager.vue new file mode 100644 index 00000000..a5ab82d7 --- /dev/null +++ b/src/pages/DrawerManagementPage/GroupMemberManager.vue @@ -0,0 +1,106 @@ + + + diff --git a/src/pages/DrawerManagementPage/GroupsTabContent.vue b/src/pages/DrawerManagementPage/GroupsTabContent.vue index f19e578c..f4733889 100644 --- a/src/pages/DrawerManagementPage/GroupsTabContent.vue +++ b/src/pages/DrawerManagementPage/GroupsTabContent.vue @@ -34,9 +34,12 @@ v-for="header in headerGroup.headers" :key="header.id" class="bg-surface-container-low" - :class="{ - 'cursor-pointer select-none': header.column.getCanSort(), - }" + :class="[ + header.column.columnDef.meta?.widthClass, + { + 'cursor-pointer select-none': header.column.getCanSort(), + }, + ]" @click="header.column.getToggleSortingHandler()?.($event)">