Skip to content

Commit

Permalink
Merge branch 'ui-rework-improvements-3' into 'main'
Browse files Browse the repository at this point in the history
Ui rework improvements 3

See merge request reportcreator/reportcreator!369
  • Loading branch information
MWedl committed Dec 14, 2023
2 parents 09174e3 + 83f9c5b commit 9473e44
Show file tree
Hide file tree
Showing 34 changed files with 108 additions and 165 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Disable buttons and menu entries when user does not have permissions
* Fix save error for user fields
* Ensure custom fonts are loaded before rendering charts and diagrams
* Remove status emoji of notes


## v2023.145 - 2023-12-11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,12 @@ class Meta:
model = ProjectNotebookPage
fields = [
'id', 'created', 'updated',
'title', 'text', 'checked', 'icon_emoji', 'status_emoji', 'assignee',
'title', 'text', 'checked', 'icon_emoji', 'assignee',
'order', 'parent',
]
extra_kwargs = {
'created': {'read_only': False, 'required': False},
'icon_emoji': {'required': False},
'status_emoji': {'required': False},
'assignee': {'required': False}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.7 on 2023-12-14 09:09

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('pentests', '0046_history_encryption'),
]

operations = [
migrations.RemoveField(
model_name='historicalprojectnotebookpage',
name='status_emoji',
),
migrations.RemoveField(
model_name='projectnotebookpage',
name='status_emoji',
),
migrations.RemoveField(
model_name='usernotebookpage',
name='status_emoji',
),
]
1 change: 0 additions & 1 deletion api/src/reportcreator_api/pentests/models/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class Meta:
text = EncryptedField(base_field=models.TextField(default=''))
checked = models.BooleanField(null=True, blank=True)
icon_emoji = models.CharField(max_length=32, null=True, blank=True)
status_emoji = models.CharField(max_length=32, null=True, blank=True)

parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True, blank=True)
order = models.PositiveIntegerField()
Expand Down
2 changes: 1 addition & 1 deletion api/src/reportcreator_api/pentests/serializers/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class NotebookPageSerializerBase(serializers.ModelSerializer):
class Meta:
fields = [
'id', 'created', 'updated', 'lock_info',
'title', 'text', 'checked', 'icon_emoji', 'status_emoji',
'title', 'text', 'checked', 'icon_emoji',
'order', 'parent',
]
extra_kwargs = {
Expand Down
2 changes: 0 additions & 2 deletions api/src/reportcreator_api/tests/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ def create_usernotebookpage(**kwargs) -> UserNotebookPage:
'text': 'Note text',
'checked': random.choice([None, True, False]),
'icon_emoji': random.choice([None, '🦖']),
'status_emoji': random.choice([None, '✔️', '🤡']),
} | kwargs)


Expand All @@ -198,7 +197,6 @@ def create_projectnotebookpage(**kwargs) -> ProjectNotebookPage:
'text': 'Note text',
'checked': random.choice([None, True, False]),
'icon_emoji': random.choice([None, '🦖']),
'status_emoji': random.choice([None, '✔️', '🤡']),
} | kwargs)


Expand Down
4 changes: 2 additions & 2 deletions api/src/reportcreator_api/tests/test_import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def test_export_import_project_all(self):

assert p.notes.count() == self.project.notes.count()
for i, s in zip(p.notes.order_by('note_id'), self.project.notes.order_by('note_id')):
assertKeysEqual(i, s, ['note_id', 'created', 'title', 'text', 'checked', 'icon_emoji', 'status_emoji', 'order'])
assertKeysEqual(i, s, ['note_id', 'created', 'title', 'text', 'checked', 'icon_emoji', 'order'])
assert i.parent.note_id == s.parent.note_id if s.parent else i.parent is None
assert {(f.name, f.file.read()) for f in p.files.all()} == {(f.name, f.file.read()) for f in self.project.files.all()}

Expand Down Expand Up @@ -373,7 +373,7 @@ def test_copy_project(self):

for p_n, cp_n in zip(p.notes.order_by('note_id'), cp.notes.order_by('note_id')):
assert p_n != cp_n
assertKeysEqual(p_n, cp_n, ['note_id', 'title', 'text', 'checked', 'icon_emoji', 'status_emoji', 'order'])
assertKeysEqual(p_n, cp_n, ['note_id', 'title', 'text', 'checked', 'icon_emoji', 'order'])
assert not cp_f.is_locked
if p_n.parent:
assert p_n.parent.note_id == cp_n.parent.note_id
Expand Down
33 changes: 16 additions & 17 deletions frontend/src/components/Design/CreateDesignDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@
:required="false"
autofocus
/>
<s-select
v-model="currentScope"
:items="scopeItems"
label="Scope"
class="mt-4"
/>
</v-card-text>

<v-card-actions>
Expand Down Expand Up @@ -52,23 +46,28 @@
<script setup lang="ts">
import { ProjectTypeScope } from "~/utils/types";
const props = withDefaults(defineProps<{
projectTypeScope: ProjectTypeScope
}>(), {
projectTypeScope: ProjectTypeScope.GLOBAL
});
const auth = useAuth();
const projectTypeStore = useProjectTypeStore();
const canCreate = computed(() => {
if (props.projectTypeScope === ProjectTypeScope.GLOBAL) {
return auth.permissions.value.designer;
} else {
return auth.permissions.value.private_designs;
}
});
const dialogVisible = ref(false);
const currentDesign = ref<ProjectType|null>(null);
const actionInProgress = ref(false);
watch(actionInProgress, () => { currentDesign.value = null });
const scopeItems = computed(() => {
return [
{ value: ProjectTypeScope.GLOBAL, title: 'Global Design', props: { subtitle: 'Available for all users', disabled: !auth.permissions.designer } },
{ value: ProjectTypeScope.PRIVATE, title: 'Private Design', props: { subtitle: 'Available only for you', disabled: !auth.permissions.private_designs } },
];
});
const currentScope = ref<ProjectTypeScope>(scopeItems.value.find(item => !item.props.disabled)?.value || ProjectTypeScope.GLOBAL);
const canCreate = computed(() => scopeItems.value.some(item => !item.props.disabled));
async function actionWrapper(action: () => Promise<ProjectType>) {
if (actionInProgress.value) {
return;
Expand All @@ -89,7 +88,7 @@ async function actionWrapper(action: () => Promise<ProjectType>) {
async function createEmptyDesign() {
return await actionWrapper(async () => {
return await projectTypeStore.create({
scope: currentScope.value,
scope: props.projectTypeScope,
name: 'New Design',
} as ProjectType);
})
Expand All @@ -99,7 +98,7 @@ async function copyDesign() {
return await actionWrapper(async () => {
return await projectTypeStore.copy({
id: currentDesign.value!.id,
scope: currentScope.value,
scope: props.projectTypeScope,
});
})
}
Expand Down
96 changes: 21 additions & 75 deletions frontend/src/components/Design/ImportDesignDialog.vue
Original file line number Diff line number Diff line change
@@ -1,87 +1,33 @@
<template>
<span>
<btn-import
ref="importBtnRef"
:import="startImport"
:loading="actionInProgress"
:disabled="!canImport"
class="ml-1 mr-1"
/>

<s-dialog v-model="dialogVisible">
<template #title>Import Design</template>
<template #default>
<v-card-text>
<s-text-field
:model-value="currentFile?.name"
label="File"
prepend-inner-icon="mdi-file"
disabled
/>
<s-select
v-model="currentScope"
:items="scopeItems"
label="Scope"
class="mt-4"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<s-btn-other
@click="dialogVisible = false"
text="Cancel"
/>
<s-btn-primary
@click="performImport"
:loading="actionInProgress"
prepend-icon="mdi-upload"
text="Import"
/>
</v-card-actions>
</template>
</s-dialog>
</span>
<btn-import
ref="importBtnRef"
:import="performImport"
:disabled="!canImport"
class="ml-1 mr-1"
/>
</template>

<script setup lang="ts">
import { ProjectTypeScope } from "~/utils/types";
const auth = useAuth();
const scopeItems = computed(() => {
return [
{ value: ProjectTypeScope.GLOBAL, title: 'Global Design', props: { subtitle: 'Available for all users', disabled: !auth.permissions.designer } },
{ value: ProjectTypeScope.PRIVATE, title: 'Private Design', props: { subtitle: 'Available only for you', disabled: !auth.permissions.private_designs } },
];
const props = withDefaults(defineProps<{
projectTypeScope: ProjectTypeScope
}>(), {
projectTypeScope: ProjectTypeScope.GLOBAL
});
const currentScope = ref<ProjectTypeScope>(scopeItems.value.find(item => !item.props.disabled)?.value || ProjectTypeScope.GLOBAL);
const canImport = computed(() => scopeItems.value.some(item => !item.props.disabled));
const currentFile = ref<File|null>(null);
const dialogVisible = ref(false);
const actionInProgress = ref(false);
function startImport(file: File) {
currentFile.value = file;
dialogVisible.value = true;
}
async function performImport() {
try {
actionInProgress.value = true;
const designs = await uploadFileHelper<ProjectType[]>('/api/v1/projecttypes/import/', currentFile.value!, { scope: currentScope.value });
await navigateTo(`/designs/${designs[0].id}/`)
} catch (error: any) {
let message = 'Import failed';
if (error?.status === 400 && error?.data?.format) {
message += ': ' + error.data.format[0];
}
requestErrorToast({ error, message });
} finally {
dialogVisible.value = false;
actionInProgress.value = false;
const auth = useAuth();
const canImport = computed(() => {
if (props.projectTypeScope === ProjectTypeScope.GLOBAL) {
return auth.permissions.value.designer;
} else {
return auth.permissions.value.private_designs;
}
});
async function performImport(file: File) {
const designs = await uploadFileHelper<ProjectType[]>('/api/v1/projecttypes/import/', file, { scope: props.projectTypeScope });
await navigateTo(`/designs/${designs[0].id}/`)
}
const importBtnRef = ref();
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/LicenseInfoMenuItem.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-list-item to="/license/" title="License" :disabled="!auth.permissions.view_license">
<v-list-item to="/license/" title="License" :disabled="!auth.permissions.value.view_license">
<template #prepend>
<v-badge :color="licenseError ? 'error' : licenseWarning ? 'warning' : 'transparent'" dot>
<v-icon icon="mdi-check-decagram" />
Expand All @@ -14,7 +14,7 @@ const apiSettings = useApiSettings();
const licenseError = computed(() => apiSettings.settings!.license.error !== null);
const { data: licenseInfo } = useLazyAsyncData(async () => {
if (auth.permissions.view_license && licenseError.value) {
if (auth.permissions.value.view_license && licenseError.value) {
await nextTick();
return await $fetch<LicenseInfoDetails>('/api/v1/utils/license/', { method: 'GET' });
} else {
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/components/NotesSortableList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
</span>
</v-list-item-subtitle>
</template>
<template #append>
<div v-if="item.note.status_emoji" class="note-icon">
<s-emoji :value="item.note.status_emoji" size="small" class="emoji-icon" />
</div>
</template>
</v-list-item>

<v-list v-if="isExpanded(item.note)" density="compact" class="pt-0 pb-0">
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/UserInfoForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ const user = computed(() => props.modelValue);
const auth = useAuth();
const apiSettings = useApiSettings();
const canEdit = computed(() => (auth.permissions.user_manager && !user.value.is_system_user) || user.value.id === auth.user.value!.id);
const canEditGeneralPermissions = computed(() => canEdit.value && props.canEditPermissions && auth.permissions.user_manager && apiSettings.settings!.features.permissions);
const canEditSuperuserPermissions = computed(() => canEdit.value && props.canEditPermissions && auth.permissions.user_manager && auth.permissions.admin);
const canEdit = computed(() => (auth.permissions.value.user_manager && !user.value.is_system_user) || user.value.id === auth.user.value!.id);
const canEditGeneralPermissions = computed(() => canEdit.value && props.canEditPermissions && auth.permissions.value.user_manager && apiSettings.settings!.features.permissions);
const canEditSuperuserPermissions = computed(() => canEdit.value && props.canEditPermissions && auth.permissions.value.user_manager && auth.permissions.value.admin);
const rules = {
required: [(v: any) => !!v || 'This field is required!'],
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/UserInfoMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
/>
<template v-if="apiSettings.isProfessionalLicense">
<v-list-item
v-if="auth.permissions.admin"
v-if="auth.permissions.value.admin"
:to="{path: '/users/self/admin/disable/', query: { next: route.fullPath }}"
prepend-icon="mdi-account-arrow-down"
title="Disable Superuser Permissions"
/>
<v-list-item
v-else-if="auth.permissions.superuser"
v-else-if="auth.permissions.value.superuser"
:to="{path: '/users/self/admin/enable/', query: { next: route.fullPath }}"
prepend-icon="mdi-account-arrow-up"
title="Enable Superuser Permissions"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/composables/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function useAuth() {
loggedIn,
user,
store,
permissions: store.permissions,
permissions: computed(() => store.permissions),
logout,
redirect,
redirectToReAuth,
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/composables/lockedit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ export function useProjectTypeLockEdit(options: {
});

const hasEditPermissions = computed(() => {
return (projectType.value.scope === ProjectTypeScope.GLOBAL && auth.permissions.designer) ||
(projectType.value.scope === ProjectTypeScope.PRIVATE && auth.permissions.private_designs) ||
return (projectType.value.scope === ProjectTypeScope.GLOBAL && auth.permissions.value.designer) ||
(projectType.value.scope === ProjectTypeScope.PRIVATE && auth.permissions.value.private_designs) ||
(projectType.value.scope === ProjectTypeScope.PROJECT && projectType.value.source === SourceEnum.CUSTOMIZED);
});
const errorMessage = computed(() => {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@
<v-list-item to="/designs/" title="Designs" prepend-icon="mdi-pencil-ruler" :active="route.path.startsWith('/designs')" />
<v-list-item to="/notes/personal/" title="Notes" prepend-icon="mdi-notebook" :active="route.path.startsWith('/notes')" />

<template v-if="auth.permissions.superuser || auth.permissions.user_manager || auth.permissions.view_license">
<template v-if="auth.permissions.value.superuser || auth.permissions.value.user_manager || auth.permissions.value.view_license">
<v-list-item class="mt-6 pa-0" min-height="0">
<v-list-subheader title="Administration" />
<template #append>
<template v-if="apiSettings.isProfessionalLicense">
<s-btn-icon
v-if="auth.permissions.admin"
v-if="auth.permissions.value.admin"
:to="{path: '/users/self/admin/disable/', query: { next: route.fullPath }}"
density="comfortable"
>
Expand All @@ -79,7 +79,7 @@
title="Users"
prepend-icon="mdi-account-multiple"
:active="route.path.startsWith('/users') && !route.path.startsWith('/users/self')"
:disabled="!auth.permissions.user_manager"
:disabled="!auth.permissions.value.user_manager"
/>
<license-info-menu-item />
</template>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/designs/[projectTypeId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<edit-toolbar v-bind="toolbarAttrs" :form="$refs.form">
<template #context-menu>
<btn-copy
:disabled="!auth.permissions.designer"
:disabled="!auth.permissions.value.designer"
:copy="performCopy"
/>
<btn-export
Expand Down
Loading

0 comments on commit 9473e44

Please sign in to comment.