refactor: user and role editing modal

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/603/head
Ryan Wang 2022-08-23 15:27:42 +08:00
parent 3722a7fb36
commit 4031eb7d85
4 changed files with 179 additions and 98 deletions

View File

@ -1,4 +1,9 @@
<script lang="ts" setup>
// core libs
import { ref } from "vue";
import type { Role } from "@halo-dev/api-client";
// components
import {
IconAddCircle,
IconArrowDown,
@ -12,44 +17,34 @@ import {
VTag,
} from "@halo-dev/components";
import RoleEditingModal from "./components/RoleEditingModal.vue";
import { onMounted, ref } from "vue";
import type { Role } from "@halo-dev/api-client";
import { apiClient } from "@halo-dev/admin-shared";
import { roleLabels } from "@/constants/labels";
// constants
import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date";
const roles = ref<Role[]>([]);
// hooks
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
const editingModal = ref<boolean>(false);
const selectedRole = ref<Role | null>(null);
const handleFetchRoles = async () => {
try {
const { data } = await apiClient.extension.role.listv1alpha1Role(0, 0, [
`!${roleLabels.TEMPLATE}`,
]);
roles.value = data.items;
} catch (e) {
console.error(e);
} finally {
selectedRole.value = null;
}
};
const { roles, handleFetchRoles } = useFetchRole();
const handleOpenEditingModal = (role: Role) => {
selectedRole.value = role;
editingModal.value = true;
};
onMounted(() => {
const onEditingModalClose = () => {
selectedRole.value = null;
handleFetchRoles();
});
};
</script>
<template>
<RoleEditingModal
v-model:visible="editingModal"
:role="selectedRole"
@close="handleFetchRoles"
@close="onEditingModalClose"
/>
<VPageHeader title="角色">

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
import { computed, ref, watch } from "vue";
import { computed, ref, watch, watchEffect } from "vue";
import { rbacAnnotations } from "@/constants/annotations";
import type { Role } from "@halo-dev/api-client";
import {
@ -10,6 +10,7 @@ import {
import cloneDeep from "lodash.clonedeep";
import { reset, submitForm } from "@formkit/core";
import { useMagicKeys } from "@vueuse/core";
import { v4 as uuid } from "uuid";
const props = withDefaults(
defineProps<{
@ -48,33 +49,39 @@ watch(
}
);
watch(props, (newVal) => {
const { Command_Enter } = useMagicKeys();
let keyboardWatcher;
if (newVal.visible) {
keyboardWatcher = watch(Command_Enter, (v) => {
if (v) {
submitForm("role-form");
}
});
} else {
keyboardWatcher?.unwatch();
}
const { Command_Enter } = useMagicKeys();
if (newVal.visible && props.role) {
formState.value = cloneDeep(props.role);
const dependencies =
props.role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES];
if (dependencies) {
selectedRoleTemplates.value = new Set(JSON.parse(dependencies));
}
return;
watchEffect(() => {
if (Command_Enter.value && props.visible) {
submitForm("role-form");
}
formState.value = cloneDeep(initialFormState);
selectedRoleTemplates.value.clear();
reset("role-form");
});
watch(
() => props.visible,
(visible) => {
if (!visible) {
handleResetForm();
}
}
);
watch(
() => props.role,
(role) => {
if (role) {
formState.value = cloneDeep(role);
const dependencies =
role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES];
if (dependencies) {
selectedRoleTemplates.value = new Set(JSON.parse(dependencies));
}
} else {
handleResetForm();
}
}
);
const tabActiveId = ref("general");
const editingModalTitle = computed(() => {
@ -84,25 +91,31 @@ const editingModalTitle = computed(() => {
const handleCreateOrUpdateRole = async () => {
try {
await handleCreateOrUpdate();
handleVisibleChange(false);
onVisibleChange(false);
} catch (e) {
console.error(e);
}
};
const handleVisibleChange = (visible: boolean) => {
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
const handleResetForm = () => {
formState.value = cloneDeep(initialFormState);
formState.value.metadata.name = uuid();
reset("role-form");
};
</script>
<template>
<VModal
:title="editingModalTitle"
:visible="visible"
:width="700"
@update:visible="handleVisibleChange"
@update:visible="onVisibleChange"
>
<VTabs v-model:active-id="tabActiveId" type="outline">
<VTabItem id="general" label="基础信息">
@ -198,7 +211,7 @@ const handleVisibleChange = (visible: boolean) => {
>
提交 +
</VButton>
<VButton @click="handleVisibleChange(false)"> Esc</VButton>
<VButton @click="onVisibleChange(false)"> Esc</VButton>
</VSpace>
</template>
</VModal>

View File

@ -1,4 +1,5 @@
import type { Role } from "@halo-dev/api-client";
import type { ComputedRef, Ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations";
@ -23,7 +24,65 @@ const initialFormState: Role = {
rules: [],
};
export function useRoleForm() {
interface useFetchRoleReturn {
roles: Ref<Role[]>;
loading: Ref<boolean>;
handleFetchRoles: () => void;
}
interface useRoleFormReturn {
formState: Ref<Role>;
initialFormState: Role;
saving: Ref<boolean>;
isUpdateMode: ComputedRef<boolean>;
handleCreateOrUpdate: () => void;
}
interface useRoleTemplateSelectionReturn {
selectedRoleTemplates: Ref<Set<string>>;
roleTemplates: Ref<Role[]>;
roleTemplateGroups: ComputedRef<RoleTemplateGroup[]>;
handleRoleTemplateSelect: (e: Event) => void;
}
/**
* Fetch all roles(without role templates) from the API.
*
* @returns {useFetchRoleReturn}
*/
export function useFetchRole(): useFetchRoleReturn {
const roles = ref<Role[]>([]);
const loading = ref(false);
const handleFetchRoles = async () => {
try {
loading.value = true;
const { data } = await apiClient.extension.role.listv1alpha1Role(0, 0, [
`!${roleLabels.TEMPLATE}`,
]);
roles.value = data.items;
} catch (e) {
console.error("Failed to fetch roles", e);
} finally {
loading.value = false;
}
};
onMounted(handleFetchRoles);
return {
roles,
loading,
handleFetchRoles,
};
}
/**
* Create or update a role.
*
* @returns {useRoleFormReturn}
*/
export function useRoleForm(): useRoleFormReturn {
const formState = ref<Role>(initialFormState);
const saving = ref(false);
@ -58,7 +117,12 @@ export function useRoleForm() {
};
}
export function useRoleTemplateSelection() {
/**
* Logic for selecting role templates
*
* @returns {useRoleTemplateSelectionReturn}
*/
export function useRoleTemplateSelection(): useRoleTemplateSelectionReturn {
const roleTemplates = ref<Role[]>([] as Role[]);
const selectedRoleTemplates = ref<Set<string>>(new Set());

View File

@ -1,7 +1,10 @@
<script lang="ts" name="UserEditingModal" setup>
import { computed, onMounted, ref, watch } from "vue";
<script lang="ts" setup>
// core libs
import { computed, ref, watch, watchEffect } from "vue";
import { apiClient } from "@halo-dev/admin-shared";
import type { Role, User } from "@halo-dev/api-client";
import type { User } from "@halo-dev/api-client";
// components
import {
IconCodeBoxLine,
IconEye,
@ -10,14 +13,21 @@ import {
VModal,
VSpace,
} from "@halo-dev/components";
// libs
import { v4 as uuid } from "uuid";
import { roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations";
import YAML from "yaml";
import cloneDeep from "lodash.clonedeep";
import { useMagicKeys } from "@vueuse/core";
import { reset, submitForm } from "@formkit/core";
// constants
import { rbacAnnotations } from "@/constants/annotations";
// hooks
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
import type { FormKitOptionsList } from "@formkit/inputs";
const props = withDefaults(
defineProps<{
visible: boolean;
@ -64,7 +74,6 @@ const initialFormState: FormState = {
raw: "",
};
const roles = ref<Role[]>([]);
const formState = ref<FormState>(cloneDeep(initialFormState));
const selectedRole = ref("");
@ -80,50 +89,59 @@ const modalWidth = computed(() => {
return formState.value.rawMode ? 800 : 700;
});
const basicRoles = computed(() => {
return roles.value.filter(
(role) => role.metadata?.labels?.[roleLabels.TEMPLATE] !== "true"
);
const { roles } = useFetchRole();
const rolesMap = computed<FormKitOptionsList>(() => {
return roles.value.map((role) => {
return {
label:
role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
role.metadata.name,
value: role.metadata?.name,
};
});
});
const { Command_Enter } = useMagicKeys();
watch(props, (newVal) => {
let keyboardWatcher;
if (newVal.visible) {
keyboardWatcher = watch(Command_Enter, (v) => {
if (v) {
submitForm("user-form");
}
});
} else {
keyboardWatcher?.unwatch();
watchEffect(() => {
if (Command_Enter.value && props.visible) {
submitForm("user-form");
}
if (newVal.visible && props.user) {
formState.value.user = cloneDeep(props.user);
return;
}
formState.value = cloneDeep(initialFormState);
reset("user-form");
});
const handleFetchRoles = async () => {
try {
const { data } = await apiClient.extension.role.listv1alpha1Role();
roles.value = data.items;
} catch (e) {
console.error(e);
watch(
() => props.visible,
(visible) => {
if (!visible) {
handleResetForm();
}
}
};
);
const handleVisibleChange = (visible: boolean) => {
watch(
() => props.user,
(user) => {
if (user) {
formState.value.user = cloneDeep(user);
} else {
handleResetForm();
}
}
);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
const handleResetForm = () => {
formState.value = cloneDeep(initialFormState);
formState.value.user.metadata.name = uuid();
reset("user-form");
};
const handleCreateUser = async () => {
try {
formState.value.saving = true;
@ -149,7 +167,7 @@ const handleCreateUser = async () => {
});
}
handleVisibleChange(false);
onVisibleChange(false);
} catch (e) {
console.error(e);
} finally {
@ -166,15 +184,13 @@ const handleRawModeChange = () => {
formState.value.user = YAML.parse(formState.value.raw);
}
};
onMounted(handleFetchRoles);
</script>
<template>
<VModal
:title="creationModalTitle"
:visible="visible"
:width="modalWidth"
@update:visible="handleVisibleChange"
@update:visible="onVisibleChange"
>
<template #actions>
<div class="modal-header-action" @click="handleRawModeChange">
@ -213,14 +229,7 @@ onMounted(handleFetchRoles);
></FormKit>
<FormKit
v-model="selectedRole"
:options="
basicRoles.map((role:Role) => {
return {
label: role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] || role.metadata.name,
value: role.metadata?.name,
};
})
"
:options="rolesMap"
label="角色"
type="select"
></FormKit>
@ -250,7 +259,7 @@ onMounted(handleFetchRoles);
>
保存 +
</VButton>
<VButton @click="handleVisibleChange(false)"> Esc</VButton>
<VButton @click="onVisibleChange(false)"> Esc</VButton>
</VSpace>
</template>
</VModal>