Skip to content

Commit f4ae1af

Browse files
committed
feat: implement search functionality in keyrings controller
- Added search capability to the KeyringsController, allowing users to filter keyrings based on name and roles using a query parameter. - Introduced a static searchFields property to define searchable fields. - Updated the settings page to include a new route for managing API keyrings.
1 parent 5be66cf commit f4ae1af

File tree

3 files changed

+169
-19
lines changed

3 files changed

+169
-19
lines changed

apps/api/src/core/keyrings/keyrings.controller.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Res } from '@nestjs/common';
1+
import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Query, Res } from '@nestjs/common';
22
import { AbstractController } from '~/_common/abstracts/abstract.controller';
33
import { Types } from 'mongoose';
44
import { ApiParam, ApiTags } from '@nestjs/swagger';
@@ -28,6 +28,11 @@ export class KeyringsController extends AbstractController {
2828
// suspendedAt: 1,
2929
};
3030

31+
protected static readonly searchFields: PartialProjectionType<any> = {
32+
name: 1,
33+
roles: 1,
34+
};
35+
3136
public constructor(private readonly _service: KeyringsService) {
3237
super();
3338
}
@@ -48,10 +53,18 @@ export class KeyringsController extends AbstractController {
4853
@Res() res: Response,
4954
@SearchFilterSchema() searchFilterSchema: FilterSchema,
5055
@SearchFilterOptions() searchFilterOptions: FilterOptions,
56+
@Query('search') search: string,
5157
): Promise<Response> {
52-
//TODO: search tree by parentId
58+
const searchFilter = {}
59+
60+
if (search && search.trim().length > 0) {
61+
searchFilter['$or'] = Object.keys(KeyringsController.searchFields).map((key) => {
62+
return { [key]: { $regex: `^${search}`, $options: 'i' } }
63+
}).filter(item => item !== undefined)
64+
}
65+
5366
const [data, total] = await this._service.findAndCount(
54-
searchFilterSchema,
67+
{ ...searchFilter, ...searchFilterSchema },
5568
KeyringsController.projection,
5669
searchFilterOptions,
5770
);
@@ -62,22 +75,6 @@ export class KeyringsController extends AbstractController {
6275
});
6376
}
6477

65-
@Get(':_id([0-9a-fA-F]{24})')
66-
@ApiParam({ name: '_id', type: String })
67-
@ApiReadResponseDecorator(KeyringsDto)
68-
public async read(
69-
@Param('_id', ObjectIdValidationPipe) _id: Types.ObjectId,
70-
@Res() res: Response,
71-
): Promise<Response> {
72-
const data = await this._service.findById(_id, {
73-
token: 0,
74-
});
75-
return res.status(HttpStatus.OK).json({
76-
statusCode: HttpStatus.OK,
77-
data,
78-
});
79-
}
80-
8178
@Delete(':_id([0-9a-fA-F]{24})')
8279
@ApiParam({ name: '_id', type: String })
8380
@ApiDeletedResponseDecorator(KeyringsDto)

apps/web/src/pages/settings.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ export default defineNuxtComponent({
9595
label: 'Tâches planifiés',
9696
_acl: '/core/cron',
9797
},
98+
{
99+
route: '/settings/keyrings',
100+
icon: 'mdi-key-chain',
101+
label: 'trousseau de clés API',
102+
_acl: '/core/keyrings',
103+
},
98104
{
99105
route: '/settings/health',
100106
icon: 'mdi-heart-pulse',
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<template lang="pug">
2+
.sesame-page
3+
.sesame-page-content
4+
sesame-core-twopan.col(
5+
table-title='Trousseau de clés API'
6+
:simple='true'
7+
:loading='pending'
8+
:rows='keyrings?.data || []'
9+
:total='keyrings?.total || 0'
10+
:columns='columns'
11+
:visible-columns='visibleColumns'
12+
:refresh='refresh'
13+
row-key='_id'
14+
)
15+
template(#top-table)
16+
sesame-core-pan-filters(:columns='columns' mode='simple' placeholder='Rechercher par nom, rôle, réseau autorisé...')
17+
template(v-slot:row-actions='{ row }')
18+
q-btn(
19+
:disable='!hasPermission("/core/keyrings", "delete")'
20+
color='negative'
21+
icon='mdi-delete'
22+
size='sm'
23+
flat
24+
round
25+
dense
26+
@click='deleteKeyring(row)'
27+
)
28+
q-tooltip.text-body2.bg-negative.text-white(
29+
v-if="!hasPermission('/core/keyrings', 'delete')"
30+
anchor="top middle"
31+
self="center middle"
32+
) Vous n'avez pas les permissions nécessaires pour effectuer cette action
33+
</template>
34+
35+
<script lang="ts">
36+
export default defineNuxtComponent({
37+
name: 'SettingsKeyringsPage',
38+
data() {
39+
return {
40+
visibleColumns: ['name', 'allowedNetworks', 'roles', 'suspendedAt'],
41+
columns: [
42+
{
43+
name: 'name',
44+
label: 'Nom',
45+
field: (row) => row.name,
46+
align: 'left',
47+
sortable: true,
48+
},
49+
{
50+
name: 'allowedNetworks',
51+
label: 'Réseaux autorisés',
52+
field: (row) => (Array.isArray(row.allowedNetworks) ? row.allowedNetworks.join(', ') : ''),
53+
align: 'left',
54+
sortable: false,
55+
},
56+
{
57+
name: 'roles',
58+
label: 'Rôles',
59+
field: (row) => (Array.isArray(row.roles) ? row.roles.join(', ') : ''),
60+
align: 'left',
61+
sortable: false,
62+
},
63+
{
64+
name: 'suspendedAt',
65+
label: 'Suspendu le',
66+
field: (row) => (row.suspendedAt ? this.$dayjs(row.suspendedAt).format('DD/MM/YYYY HH:mm:ss') : 'N/A'),
67+
align: 'left',
68+
sortable: true,
69+
},
70+
],
71+
}
72+
},
73+
async setup() {
74+
const { useHttpPaginationOptions, useHttpPaginationReactive } = usePagination()
75+
const { hasPermission } = useAccessControl()
76+
const paginationOptions = useHttpPaginationOptions()
77+
78+
const {
79+
data: keyrings,
80+
error,
81+
pending,
82+
refresh,
83+
execute,
84+
} = await useHttp<any>('/core/keyrings', {
85+
method: 'get',
86+
...paginationOptions,
87+
})
88+
if (error.value) {
89+
console.error(error.value)
90+
throw showError({
91+
statusCode: 500,
92+
statusMessage: 'Internal Server Error',
93+
})
94+
}
95+
96+
useHttpPaginationReactive(paginationOptions, execute)
97+
98+
return {
99+
keyrings,
100+
pending,
101+
refresh,
102+
hasPermission,
103+
}
104+
},
105+
methods: {
106+
async deleteKeyring(keyring: Record<string, any>): Promise<void> {
107+
this.$q
108+
.dialog({
109+
title: 'Confirmation',
110+
message: 'Voulez-vous vraiment supprimer cette clé API ?',
111+
persistent: true,
112+
ok: {
113+
push: true,
114+
color: 'positive',
115+
label: 'Supprimer',
116+
},
117+
cancel: {
118+
push: true,
119+
color: 'negative',
120+
label: 'Annuler',
121+
},
122+
})
123+
.onOk(() => {
124+
this.$http
125+
.delete(`/core/keyrings/${keyring._id}`)
126+
.then(() => {
127+
this.$q.notify({
128+
message: 'La clé API a été supprimée.',
129+
color: 'positive',
130+
position: 'top-right',
131+
icon: 'mdi-check-circle-outline',
132+
})
133+
this.refresh()
134+
})
135+
.catch((error: any) => {
136+
this.$q.notify({
137+
message: `Impossible de supprimer la clé API : ${error?.response?._data?.message || 'erreur inconnue'}`,
138+
color: 'negative',
139+
position: 'top-right',
140+
icon: 'mdi-alert-circle-outline',
141+
})
142+
})
143+
})
144+
},
145+
},
146+
})
147+
</script>

0 commit comments

Comments
 (0)