diff --git a/components/probe/ProbeFilters/AdminFilterSettings.vue b/components/probe/ProbeFilters/AdminFilterSettings.vue new file mode 100644 index 00000000..131f25bc --- /dev/null +++ b/components/probe/ProbeFilters/AdminFilterSettings.vue @@ -0,0 +1,30 @@ + + + diff --git a/components/FilterSettings.vue b/components/probe/ProbeFilters/MobileProbeListFilters.vue similarity index 61% rename from components/FilterSettings.vue rename to components/probe/ProbeFilters/MobileProbeListFilters.vue index 81f5c5ee..64d8ac5f 100644 --- a/components/FilterSettings.vue +++ b/components/probe/ProbeFilters/MobileProbeListFilters.vue @@ -41,7 +41,7 @@ Filter: - + @@ -85,33 +85,33 @@ + +
diff --git a/components/probe/ProbeFilters/ProbeListFilters.vue b/components/probe/ProbeFilters/ProbeListFilters.vue new file mode 100644 index 00000000..a5bdb833 --- /dev/null +++ b/components/probe/ProbeFilters/ProbeListFilters.vue @@ -0,0 +1,120 @@ + + + diff --git a/composables/useProbeFilters.ts b/composables/useProbeFilters.ts index f60320fb..ad9f5708 100644 --- a/composables/useProbeFilters.ts +++ b/composables/useProbeFilters.ts @@ -3,8 +3,10 @@ import { ref } from 'vue'; import { useRoute } from 'vue-router'; import { useUserFilter } from '~/composables/useUserFilter'; import { ONLINE_STATUSES, OFFLINE_STATUSES } from '~/constants/probes'; +import { useAuth } from '~/store/auth'; export type StatusCode = 'all' | 'online' | 'ping-test-failed' | 'offline' | 'online-outdated'; +export type AdoptionOption = 'all' | 'adopted' | 'non-adopted'; type StatusOption = { name: string; @@ -17,9 +19,10 @@ export interface Filter { status: StatusCode; by: string; desc: boolean; + adoption: AdoptionOption; } -const DEFAULT_FILTER: Filter = { search: '', status: 'all', by: 'name', desc: false } as const; +const DEFAULT_FILTER: Filter = { search: '', status: 'all', by: 'name', desc: false, adoption: 'all' } as const; export const SORTABLE_FIELDS: string[] = [ 'name', 'location', 'tags' ] as const; @@ -31,12 +34,15 @@ export const STATUS_MAP: Record = { 'offline': { name: 'Offline', options: OFFLINE_STATUSES }, } as const; +export const ADOPTION_OPTIONS: string[] = [ 'all', 'adopted', 'non-adopted' ] as const; + interface ProbeFiltersOptions { active?: MaybeRefOrGetter; } export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = {}) => { const route = useRoute(); + const auth = useAuth(); const { getUserFilter } = useUserFilter(); const filter = ref({ ...DEFAULT_FILTER }); @@ -46,8 +52,8 @@ export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = { const { sortField = '', sortOrder = 1 } = event; if (!sortOrder || typeof sortField !== 'string' || !SORTABLE_FIELDS.includes(sortField)) { - filter.value.by = 'name'; - filter.value.desc = false; + filter.value.by = DEFAULT_FILTER.by; + filter.value.desc = DEFAULT_FILTER.desc; } else { filter.value.by = sortField; filter.value.desc = sortOrder === -1; @@ -68,9 +74,10 @@ export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = { const constructQuery = () => ({ ...filter.value.search && { filter: filter.value.search }, - ...filter.value.by !== 'name' && { by: filter.value.by }, + ...!isDefault('by') && { by: filter.value.by }, ...filter.value.desc && { desc: 'true' }, - ...filter.value.status !== 'all' && { status: filter.value.status }, + ...!isDefault('status') && { status: filter.value.status }, + ...auth.isAdmin && !isDefault('adoption') && { adoption: filter.value.adoption }, }); const onParamChange = () => { @@ -100,19 +107,33 @@ export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = { } }; - const getCurrentFilter = (includeStatus: boolean = false) => ({ - ...getUserFilter('userId'), - ...filter.value.search && { searchIndex: { _icontains: filter.value.search } }, - ...includeStatus && filter.value.status !== 'all' && { status: { _in: STATUS_MAP[filter.value.status].options } }, - ...includeStatus && filter.value.status === 'online-outdated' && { isOutdated: { _eq: true } }, - }); + const getCurrentFilter = (ignoredFields: Array = []) => getDirectusFilter(filter, ignoredFields); + + const getDirectusFilter = (filter: MaybeRefOrGetter, ignoredFields: Array = []) => { + const filterValue = toValue(filter); + + return { + ...getUserFilter('userId'), + ...filterValue.search && { searchIndex: { _icontains: filterValue.search } }, + ...!ignoredFields.includes('status') && !isDefault('status', filter) && { status: { _in: STATUS_MAP[filterValue.status].options } }, + ...!ignoredFields.includes('status') && filterValue.status === 'online-outdated' && { isOutdated: { _eq: true } }, + ...!ignoredFields.includes('adoption') && auth.isAdmin && !isDefault('adoption', filter) && { + userId: filterValue.adoption === 'adopted' ? { _neq: null } : { _eq: null }, + }, + }; + }; + + const isDefault = (field: keyof Filter, filterObj: MaybeRefOrGetter = filter) => { + return toValue(filterObj)[field] === DEFAULT_FILTER[field]; + }; watch([ () => route.query.filter, () => route.query.by, () => route.query.desc, () => route.query.status, - ], async ([ search, by, desc, status ]) => { + () => route.query.adoption, + ], async ([ search, by, desc, status, adoption ]) => { if (!toValue(active)) { return; } @@ -140,6 +161,12 @@ export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = { } else { filter.value.status = DEFAULT_FILTER.status; } + + if (typeof adoption === 'string' && ADOPTION_OPTIONS.includes(adoption) && auth.isAdmin) { + filter.value.adoption = adoption as AdoptionOption; + } else { + filter.value.adoption = DEFAULT_FILTER.adoption; + } }, { immediate: true }); return { @@ -149,10 +176,13 @@ export const useProbeFilters = ({ active = () => true }: ProbeFiltersOptions = { // handlers onSortChange, onFilterChange, - onStatusChange: () => onParamChange(), + onParamChange, onBatchChange, // builders getSortSettings, getCurrentFilter, + getDirectusFilter, + // helpers + isDefault, }; }; diff --git a/pages/notifications.vue b/pages/notifications.vue index 6dbe28c0..02bca8ae 100644 --- a/pages/notifications.vue +++ b/pages/notifications.vue @@ -102,7 +102,7 @@ { default: () => [], watch: [ first, itemsPerPage ] }, ); - const { data: notificationsCount, error: notificationCntError } = await useLazyAsyncData( + const { data: notificationsCount, error: notificationCountError } = await useLazyAsyncData( 'directus_notifications_cnt', () => $directus.request<{ count: { id: number } }[]>(readNotifications({ filter: getUserFilter('recipient'), @@ -113,7 +113,7 @@ { default: () => 0, transform: data => data?.[0]?.count?.id ?? 0 }, ); - useErrorToast(notificationError, notificationCntError); + useErrorToast(notificationError, notificationCountError); notificationBus.on((idsToArchive) => { notifications.value.forEach((notification) => { diff --git a/pages/probes/index.vue b/pages/probes/index.vue index 5a8ce385..969a1ed1 100644 --- a/pages/probes/index.vue +++ b/pages/probes/index.vue @@ -19,7 +19,7 @@ Adopt a probe -
+

List of probes

- -
- Status: - - - - - - - -
+
@@ -100,8 +56,14 @@
- -

{{ slotProps.data.name || slotProps.data.city }}

+ +

+ {{ slotProps.data.name || slotProps.data.city }}, + + {{slotProps.data.user?.github_username ?? 'not adopted'}} + + +

{{ slotProps.data.ip }}

@@ -179,10 +141,8 @@ ref="mobileFiltersRef" class="!left-1/2 w-[95%] !-translate-x-1/2 !transform p-6 [&>*]:border-none" role="dialog"> - @@ -194,9 +154,15 @@
- -
-

{{ probe.name || probe.city }}

+ +
+

+ {{ probe.name || probe.city }}, + + {{probe.user?.github_username ?? 'not adopted'}} + + +

{{ probe.ip }}

@@ -231,7 +197,7 @@
Number of probes: - {{ statusCounts[filter.status] }} + {{ filteredProbeCount }}