Skip to content

Commit

Permalink
List views: tabs instead of filters
Browse files Browse the repository at this point in the history
  • Loading branch information
MWedl committed Dec 13, 2023
1 parent b65a92c commit 23f18f4
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 349 deletions.
8 changes: 7 additions & 1 deletion frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,10 @@ const theme = computed(() => {

<!-- TODO: [x] font: Noto Sans instead of Exo 2 -->
<!-- TODO: [x] container without fluid for settings pages and lists -->
<!-- TODO: [ ] list views: tabs below list instead of filters -->
<!-- TODO: [x] list views: tabs below list instead of filters -->
<!-- TODO: [x] remove s-sub-menu -->
<!-- TODO: [x] use <full-height-page> in <list-view> -->
<!-- TODO: [x] refactor project list -->
<!-- TODO: [x] refactor design list -->
<!-- TODO: [ ] how to handle scope selection in designs? -->
<!-- TODO: [ ] main drawer: isActive based on route path start instead of default -->
69 changes: 39 additions & 30 deletions frontend/src/components/ListView.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
<template>
<v-container class="pt-0">
<v-list v-if="items" class="pt-0 overflow-visible">
<div class="list-header pt-2 mb-4">
<h1>
<slot name="title" />
<full-height-page>
<v-container class="pt-0">
<v-list v-if="items" class="pt-0 overflow-visible">
<div class="list-header pt-2 mb-4">
<h1>
<slot name="title" />

<div v-if="$slots.actions" class="list-header-actions">
<slot name="actions" />
</div>
</h1>
<div v-if="$slots.actions" class="list-header-actions">
<slot name="actions" />
</div>
</h1>

<slot name="searchbar" :items="items">
<v-text-field
:model-value="items.search.value"
@update:model-value="updateSearch"
label="Search"
variant="underlined"
spellcheck="false"
hide-details="auto"
autofocus
class="mt-0 mb-2"
/>
</slot>
</div>
<slot name="searchbar" :items="items">
<v-text-field
:model-value="items.search.value"
@update:model-value="updateSearch"
label="Search"
variant="underlined"
spellcheck="false"
hide-details="auto"
autofocus
class="mt-0 mb-2"
/>
</slot>
<v-tabs v-if="$slots.tabs" height="30" selected-class="text-primary" class="list-header-tabs">
<slot name="tabs" />
</v-tabs>
</div>

<slot v-for="item in items.data.value" name="item" :item="item" />
<page-loader :items="items" class="mt-4" />
<v-list-item
v-if="items.data.value.length === 0 && !items.hasNextPage.value"
text="No data found"
/>
</v-list>
</v-container>
<slot v-for="item in items.data.value" name="item" :item="item" />
<page-loader :items="items" class="mt-4" />
<v-list-item
v-if="items.data.value.length === 0 && !items.hasNextPage.value"
text="No data found"
/>
</v-list>
</v-container>
</full-height-page>
</template>

<script setup lang="ts" generic="T">
Expand Down Expand Up @@ -86,4 +91,8 @@ defineExpose({
margin-left: 0.4rem;
}
}
.list-header-tabs:deep(.v-tab) {
text-transform: initial;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/ProInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ const apiSettings = useApiSettings();
}
.pro-badge {
color: rgb(var(--v-theme-primary));
font-weight: 800;
font-weight: 900;
}
</style>
11 changes: 0 additions & 11 deletions frontend/src/components/S/SubMenu.vue

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ head.hooks.hook('dom:beforeRender', syncBreadcrumbs);
height: 28px;
display: flex;
flex-direction: column-reverse;
font-weight: 800;
font-weight: 900;
font-size: 1.5rem;
line-height: 1;
color: rgb(var(--v-theme-logo));
Expand Down
81 changes: 14 additions & 67 deletions frontend/src/pages/designs/index.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,19 @@
<template>
<file-drop-area @drop="importBtnRef.performImport($event)" class="h-100">
<full-height-page>
<list-view ref="listViewRef" url="/api/v1/projecttypes/?scope=global&ordering=name">
<template #title>Designs</template>
<template #actions>
<design-create-design-dialog />
<design-import-design-dialog ref="importBtnRef" />
</template>
<template #searchbar="{ items }">
<v-row dense class="mb-2 w-100">
<v-col cols="12" md="10">
<v-text-field
:model-value="items.search.value"
@update:model-value="listViewRef?.updateSearch"
label="Search"
spellcheck="false"
hide-details="auto"
variant="underlined"
autofocus
class="ma-0"
/>
</v-col>
<v-col cols="12" md="2">
<s-select
v-model="designScopeFilter"
:items="[{ title: 'All Designs', value: designFilterAllValue }, { title: 'Global Designs', value: ProjectTypeScope.GLOBAL}, { title: 'Private Designs', value: ProjectTypeScope.PRIVATE }]"
label="Scope"
variant="underlined"
class="ma-0"
/>
</v-col>
</v-row>
</template>
<template #item="{item}">
<v-list-item :to="`/designs/${item.id}/pdfdesigner/`" lines="two">
<v-list-item-title>{{ item.name }}</v-list-item-title>
<v-list-item-subtitle>
<v-chip v-if="item.scope === ProjectTypeScope.GLOBAL" size="small" class="ma-1">
<v-icon size="small" start icon="mdi-earth" />
Global Design
</v-chip>
<v-chip v-else-if="item.scope === ProjectTypeScope.PRIVATE" size="small" class="ma-1">
<v-icon size="small" start icon="mdi-account" />
Private Design
</v-chip>
<chip-created :value="item.created" />
</v-list-item-subtitle>
</v-list-item>
</template>
</list-view>
</full-height-page>
<list-view url="/api/v1/projecttypes/?scope=global&ordering=name">
<template #title>Designs</template>
<template #actions>
<design-create-design-dialog />
<design-import-design-dialog ref="importBtnRef" />
</template>
<template #tabs>
<v-tab :to="{ path: '/designs/', query: route.query }" exact prepend-icon="mdi-earth" text="Global Designs" />
<v-tab :to="{ path: '/designs/private/', query: route.query }" prepend-icon="mdi-account" text="Private Designs" />
</template>
<template #item="{item}">
<v-list-item :to="`/designs/${item.id}/pdfdesigner/`" :title="item.name" />
</template>
</list-view>
</file-drop-area>
</template>

Expand All @@ -63,23 +27,6 @@ useHeadExtended({
});
const route = useRoute();
const router = useRouter();
const importBtnRef = ref();
const listViewRef = ref();
const designFilterAllValue = String([ProjectTypeScope.GLOBAL, ProjectTypeScope.PRIVATE]);
const designScopeFilter = computed({
get: () => {
const currentScope = String(route.query.scope || '');
if (!currentScope) {
return ProjectTypeScope.GLOBAL;
} else {
return currentScope;
}
},
set: (val) => {
router.replace({ query: { ...route.query, scope: val.split(',') } });
}
});
</script>
32 changes: 32 additions & 0 deletions frontend/src/pages/designs/private.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<file-drop-area @drop="importBtnRef.performImport($event)" class="h-100">
<list-view url="/api/v1/projecttypes/?scope=private&ordering=name">
<template #title>Designs</template>
<template #actions>
<design-create-design-dialog />
<design-import-design-dialog ref="importBtnRef" />
</template>
<template #tabs>
<v-tab :to="{ path: '/designs/', query: route.query }" exact prepend-icon="mdi-earth" text="Global Designs" />
<v-tab :to="{ path: '/designs/private/', query: route.query }" prepend-icon="mdi-account" text="Private Designs" />
</template>
<template #item="{item}">
<v-list-item :to="`/designs/${item.id}/pdfdesigner/`" :title="item.name" />
</template>
</list-view>
</file-drop-area>
</template>

<script setup lang="ts">
definePageMeta({
title: 'Designs',
toplevel: true,
});
useHeadExtended({
breadcrumbs: () => designListBreadcrumbs(),
});
const route = useRoute();
const importBtnRef = ref();
</script>
90 changes: 44 additions & 46 deletions frontend/src/pages/projects/archived/index.vue
Original file line number Diff line number Diff line change
@@ -1,51 +1,47 @@
<template>
<full-height-page>
<template #header>
<s-sub-menu>
<v-tab to="/projects/" exact text="Projects" />
<v-tab to="/projects/archived/" text="Archived Projects" />
</s-sub-menu>
<list-view url="/api/v1/archivedprojects/">
<template #title>Projects</template>
<template #tabs>
<v-tab :to="{path: '/projects/', query: route.query}" exact prepend-icon="mdi-file-document" text="Active Projects" />
<v-tab :to="{path: '/projects/finished/', query: route.query}" prepend-icon="mdi-flag-checkered" text="Finished Projects" />
<v-tab :to="{path: '/projects/archived/', query: route.query}" prepend-icon="mdi-folder-lock-outline" text="Archived Projects" />
</template>

<list-view url="/api/v1/archivedprojects/">
<template #title>Archived Projects</template>
<template #item="{item}: { item: ArchivedProject}">
<v-list-item :to="`/projects/archived/${item.id}/`" lines="two">
<v-list-item-title> {{ item.name }}</v-list-item-title>

<v-list-item-subtitle>
<chip-created :value="item.created" />
<chip-auto-delete :value="item.auto_delete_date" />

<v-chip size="small" class="ma-1">
<v-icon v-if="item.key_parts.some(p => !p.user.is_active) && item.key_parts.filter(p => p.user.is_active).length < item.threshold * 2" size="small" start color="warning" icon="mdi-alert" />
{{ item.threshold }} / {{ item.key_parts.length }}

<s-tooltip activator="parent">
{{ item.threshold }} of {{ item.key_parts.length }} users are required to restore this project
</s-tooltip>
</v-chip>

<v-chip
v-for="keypart in item.key_parts" :key="keypart.id"
class="ma-1" size="small"
>
<v-icon v-if="keypart.is_decrypted" size="small" start color="success" icon="mdi-lock-open-variant" />
<v-icon v-else size="small" start color="red" icon="mdi-lock" />
{{ keypart.user.username }}

<s-tooltip activator="parent">
<span v-if="keypart.is_decrypted">{{ keypart.user.username }} already restored their part</span>
<span v-else>{{ keypart.user.username }}'s part is still encrypted</span>
</s-tooltip>
</v-chip>

<chip-tag v-for="tag in item.tags" :key="tag" :value="tag" class="mt-2" />
</v-list-item-subtitle>
</v-list-item>
</template>
</list-view>
</full-height-page>
<template #item="{item}: { item: ArchivedProject}">
<v-list-item :to="`/projects/archived/${item.id}/`" lines="two">
<v-list-item-title> {{ item.name }}</v-list-item-title>

<v-list-item-subtitle>
<chip-created :value="item.created" />
<chip-auto-delete :value="item.auto_delete_date" />

<v-chip size="small" class="ma-1">
<v-icon v-if="item.key_parts.some(p => !p.user.is_active) && item.key_parts.filter(p => p.user.is_active).length < item.threshold * 2" size="small" start color="warning" icon="mdi-alert" />
{{ item.threshold }} / {{ item.key_parts.length }}

<s-tooltip activator="parent">
{{ item.threshold }} of {{ item.key_parts.length }} users are required to restore this project
</s-tooltip>
</v-chip>

<v-chip
v-for="keypart in item.key_parts" :key="keypart.id"
class="ma-1" size="small"
>
<v-icon v-if="keypart.is_decrypted" size="small" start color="success" icon="mdi-lock-open-variant" />
<v-icon v-else size="small" start color="red" icon="mdi-lock" />
{{ keypart.user.username }}

<s-tooltip activator="parent">
<span v-if="keypart.is_decrypted">{{ keypart.user.username }} already restored their part</span>
<span v-else>{{ keypart.user.username }}'s part is still encrypted</span>
</s-tooltip>
</v-chip>

<chip-tag v-for="tag in item.tags" :key="tag" :value="tag" class="mt-2" />
</v-list-item-subtitle>
</v-list-item>
</template>
</list-view>
</template>

<script setup lang="ts">
Expand All @@ -56,4 +52,6 @@ definePageMeta({
useHeadExtended({
breadcrumbs: () => archivedProjectListBreadcrumbs(),
});
const route = useRoute();
</script>
28 changes: 28 additions & 0 deletions frontend/src/pages/projects/finished.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<list-view url="/api/v1/pentestprojects/?readonly=true">
<template #title>Projects</template>
<template #tabs>
<v-tab :to="{path: '/projects/', query: route.query}" exact prepend-icon="mdi-file-document" text="Active Projects" />
<v-tab :to="{path: '/projects/finished/', query: route.query}" prepend-icon="mdi-flag-checkered" text="Finished Projects" />
<v-tab :to="{path: '/projects/archived/', query: route.query}" :disabled="!apiSettings.settings!.features.archiving" prepend-icon="mdi-folder-lock-outline">
<pro-info>Archived Projects</pro-info>
</v-tab>
</template>
<template #item="{item}">
<project-list-item :item="item" />
</template>
</list-view>
</template>

<script setup lang="ts">
definePageMeta({
title: 'Projects',
toplevel: true,
});
useHeadExtended({
breadcrumbs: () => projectListBreadcrumbs(),
});
const route = useRoute();
const apiSettings = useApiSettings();
</script>
Loading

0 comments on commit 23f18f4

Please sign in to comment.