Make event objects more extensible

This commit is contained in:
cuigh
2021-12-24 15:04:15 +08:00
parent 308d3ddc05
commit 752ddff01f
24 changed files with 208 additions and 384 deletions

View File

@@ -4,8 +4,9 @@ export interface Event {
id: string;
type: string;
action: string;
code: number;
name: string;
args: {
[key: string]: string;
};
userId: string;
username: string;
time: number;

View File

@@ -291,236 +291,3 @@ export function createChart(dom: HTMLElement, info: ChartInfo): Chart {
throw new Error('unknown chart type: ' + info.type)
}
}
// export class ChartDashboardOptions {
// name: string;
// key?: string;
// period?: number = 30;
// refreshInterval?: number = 15; // seconds
// }
// export class ChartDashboard {
// private $panel: JQuery;
// private readonly opts: ChartDashboardOptions;
// private charts: Chart[] = [];
// private timer: number;
// private dlg: ChartDialog;
// constructor(elem: string | Element | JQuery, charts: ChartData[], opts?: ChartDashboardOptions) {
// this.opts = $.extend(new ChartDashboardOptions(), opts);
// this.$panel = $(elem);
// this.dlg = new ChartDialog(this);
// charts.forEach(opts => this.createGraph(opts));
// // events
// Dispatcher.bind(this.$panel).on("remove-chart", e => {
// let name = $(e.target).closest("div.column").data("name");
// Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
// this.removeGraph(name);
// dlg.close();
// });
// });
// $(window).resize(e => {
// $.each(this.charts, (i: number, g: Chart) => {
// g.resize();
// });
// });
// this.refresh();
// }
// refresh() {
// if (!this.timer) {
// this.loadData();
// if (this.opts.refreshInterval > 0) {
// this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
// }
// }
// }
// private refreshData() {
// this.loadData();
// if (this.opts.refreshInterval > 0) {
// this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
// }
// }
// stop() {
// clearTimeout(this.timer);
// this.timer = 0;
// }
// setPeriod(period: number) {
// this.opts.period = period;
// this.loadData();
// }
// addGraph(opts: ChartData) {
// this.createGraph(opts);
// this.loadData();
// }
// private createGraph(opts: ChartData) {
// for (let i = 0; i < this.charts.length; i++) {
// let chart = this.charts[i];
// if (chart.getOptions().name === opts.name) {
// // chart already added.
// return;
// }
// }
// let chart = ChartFactory.create(opts);
// if (chart != null) {
// this.$panel.append(chart.getElem());
// chart.init();
// this.charts.push(chart);
// }
// }
// removeGraph(name: string) {
// let index = -1;
// for (let i = 0; i < this.charts.length; i++) {
// let c = this.charts[i];
// if (c.getOptions().name === name) {
// index = i;
// break;
// }
// }
// if (index >= 0) {
// let $elem = this.charts[index].getElem();
// this.charts.splice(index, 1);
// $elem.remove();
// }
// }
// save(asDefault: boolean = false) {
// let charts: any = [];
// this.$panel.children().each((index: number, elem: Element) => {
// let name = $(elem).data("name");
// for (let i = 0; i < this.charts.length; i++) {
// let c = this.charts[i];
// if (c.getOptions().name === name) {
// charts.push({
// name: c.getOptions().name,
// width: c.getOptions().width,
// height: c.getOptions().height,
// });
// break;
// }
// }
// });
// let args = {
// name: this.opts.name,
// key: asDefault ? '' : (this.opts.key || ''),
// charts: charts,
// };
// $ajax.post(`/system/chart/save_dashboard`, args).json<AjaxResult>((r: AjaxResult) => {
// if (r.success) {
// Notification.show("success", "Successfully saved.");
// } else {
// Notification.show("danger", r.message);
// }
// });
// }
// getOptions(): ChartDashboardOptions {
// return this.opts;
// }
// private loadData() {
// if (this.charts.length == 0) {
// return
// }
// let args: any = {
// charts: this.charts.map(c => c.getOptions().name).join(","),
// period: this.opts.period,
// };
// if (this.opts.key) {
// args.key = this.opts.key;
// }
// $ajax.get(`/system/chart/data`, args).json((d: { [index: string]: any[] }) => {
// $.each(this.charts, (i: number, g: Chart) => {
// if (d[g.getOptions().name]) {
// g.setData(d[g.getOptions().name]);
// }
// });
// });
// }
// }
// class ChartDialog {
// private dashboard: ChartDashboard;
// private fb: FilterBox;
// private charts: any;
// private $charts: JQuery;
// constructor(dashboard: ChartDashboard) {
// this.dashboard = dashboard;
// this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
// $("#btn-add").click(this.showAddDlg.bind(this));
// $("#btn-add-chart").click(this.addChart.bind(this));
// $("#btn-save").click(() => {
// this.dashboard.save();
// });
// $("#btn-save-as-default").click(() => {
// this.dashboard.save(true);
// });
// }
// private showAddDlg() {
// let $panel = $("#nav-charts");
// $panel.find("label.panel-block").remove();
// // load charts
// $ajax.get(`/system/chart/query`, { dashboard: this.dashboard.getOptions().name }).json((charts: any) => {
// for (let i = 0; i < charts.length; i++) {
// let c = charts[i];
// $panel.append(`<label class="panel-block">
// <input type="checkbox" value="${c.name}" data-index="${i}">${c.name}: ${c.title}
// </label>`);
// }
// this.charts = charts;
// this.$charts = $panel.find("label.panel-block");
// });
// let dlg = new Modal("#dlg-add-chart");
// dlg.show();
// }
// private filterCharts(text: string) {
// if (!text) {
// this.$charts.show();
// return;
// }
// this.$charts.each((i, elem) => {
// let $elem = $(elem);
// let texts: string[] = [
// this.charts[i].name.toLowerCase(),
// this.charts[i].title.toLowerCase(),
// this.charts[i].desc.toLowerCase(),
// ];
// for (let i = 0; i < texts.length; i++) {
// let index = texts[i].indexOf(text);
// if (index >= 0) {
// $elem.show();
// return;
// }
// }
// $elem.hide();
// })
// }
// private addChart() {
// this.$charts.each((i, e) => {
// if ($(e).find(":checked").length > 0) {
// let c = this.charts[i];
// this.dashboard.addGraph(c);
// }
// });
// Modal.close();
// }
// }

View File

@@ -1,8 +1,8 @@
<template>
<div class="tab-pane" :class="{active: active}" :style="{borderColor: themeVars.primaryColor}">
<div class="tab-pane" :class="{ active: active }" :style="{ borderColor: themeVars.primaryColor }">
<router-link
:to="href"
:style="{color: active ? themeVars.primaryColor : themeVars.textColorBase}"
:style="{ color: active ? themeVars.primaryColor : themeVars.textColorBase }"
>
<slot />
</router-link>
@@ -11,16 +11,15 @@
<script setup lang="ts">
import { useThemeVars } from "naive-ui";
import { RouterLink } from "vue-router";
import { RouterLink, RouteLocationRaw } from "vue-router";
const props = defineProps({
active: Boolean,
title: String,
href: {
type: String,
required: true,
},
});
interface Props {
active?: boolean;
title?: string;
href: RouteLocationRaw;
}
const props = defineProps<Props>()
const themeVars = useThemeVars();
</script>

View File

@@ -1,7 +1,7 @@
<template>
<x-page-header>
<template #action>
<n-button secondary size="small" @click="$router.push('/system/charts')">
<n-button secondary size="small" @click="$router.push({ name: 'chart_list' })">
<template #icon>
<n-icon>
<back-icon />
@@ -219,7 +219,7 @@ const rules: any = {
const form = ref();
const { submit, submiting } = useForm(form, () => chartApi.save(model.value), () => {
window.message.info(t('texts.action_success'));
router.push("/system/charts")
router.push({ name: 'chart_list' })
})
function newMetric() {

View File

@@ -9,18 +9,24 @@
</template>
{{ t('buttons.import') }}
</n-button>
<n-button secondary size="small" @click="$router.push('/system/charts/new')">
<n-button secondary size="small" @click="$router.push({ name: 'chart_new' })">
<template #icon>
<n-icon>
<add-icon />
</n-icon>
</template>{{ t('buttons.new') }}
</template>
{{ t('buttons.new') }}
</n-button>
</template>
</x-page-header>
<n-space class="page-body" vertical :size="12">
<n-space :size="12">
<n-input size="small" v-model:value="filter.title" :placeholder="t('fields.title')" clearable />
<n-input
size="small"
v-model:value="filter.title"
:placeholder="t('fields.title')"
clearable
/>
<n-select
size="small"
:placeholder="t('fields.dashboard')"
@@ -84,7 +90,7 @@ const columns = [
title: t('fields.title'),
key: "title",
fixed: "left" as const,
render: (c: Chart) => renderLink(`/system/charts/${c.id}`, c.title),
render: (c: Chart) => renderLink({ name: 'chart_detail', params: { id: c.id } }, c.title),
},
{
title: t('fields.type'),
@@ -136,7 +142,7 @@ const columns = [
{
type: 'warning',
text: t('buttons.edit'),
action: () => router.push(`/system/charts/${c.id}/edit`),
action: () => router.push({ name: 'chart_edit', params: { id: c.id } }),
},
{
type: 'info',

View File

@@ -1,7 +1,7 @@
<template>
<x-page-header :subtitle="model.title">
<template #action>
<n-button secondary size="small" @click="$router.push('/system/charts')">
<n-button secondary size="small" @click="$router.push({ name: 'chart_list' })">
<template #icon>
<n-icon>
<back-icon />
@@ -12,7 +12,7 @@
<n-button
secondary
size="small"
@click="$router.push(`/system/charts/${model.id}/edit`)"
@click="$router.push({ name: 'chart_edit', params: { id: model.id } })"
>{{ t('buttons.edit') }}</n-button>
</template>
</x-page-header>
@@ -55,13 +55,19 @@
<x-description-item :label="t('fields.dashboard')">{{ model.dashboard }}</x-description-item>
<x-description-item :label="t('fields.type')">{{ model.type }}</x-description-item>
<x-description-item :label="t('fields.created_by')">
<x-anchor :url="`/system/users/${model.createdBy?.id}`">{{ model.createdBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.createdBy?.id } }"
v-if="model.createdBy?.id"
>{{ model.createdBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item>
<x-description-item :label="t('fields.updated_by')">
<x-anchor :url="`/system/users/${model.updatedBy?.id}`">{{ model.updatedBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.updatedBy?.id } }"
v-if="model.updatedBy?.id"
>{{ model.updatedBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />

View File

@@ -153,7 +153,6 @@ const columns = [
key: "id",
width: 210,
fixed: "left" as const,
// render: (e: Event) => renderLink(`/system/events/${e.id}`, e.id),
},
{
title: t('fields.type'),
@@ -172,15 +171,12 @@ const columns = [
{
title: t('fields.object'),
key: "name",
render(e: Event) {
const u = url(e)
return u ? renderLink(u, e.name) : e.name
},
render: renderObject,
},
{
title: t('fields.operator'),
key: "name",
render: (e: Event) => renderLink(`/system/users/${e.userId}`, e.username),
render: (e: Event) => e.userId ? renderLink({ name: 'user_detail', params: { id: e.userId } }, e.username) : null,
},
{
title: t('fields.time'),
@@ -190,40 +186,40 @@ const columns = [
];
const { state, pagination, fetchData, changePageSize } = useDataTable(eventApi.search, filter)
function url(e: Event): RouteLocationRaw | null {
if (e.type === 'Setting') {
return { name: 'setting' }
} else if (!e.code) {
return null
}
function renderObject(e: Event) {
switch (e.type) {
case "User":
return { name: 'user_detail', params: { id: e.code } }
case "Role":
return { name: 'role_detail', params: { id: e.code } }
case "Chart":
return { name: 'chart_detail', params: { id: e.code } }
case "Registry":
return { name: 'registry_detail', params: { id: e.code } }
case "Node":
return { name: 'node_detail', params: { id: e.code } }
case "Network":
return { name: 'network_detail', params: { name: e.code } }
case "Service":
return { name: 'service_detail', params: { name: e.code } }
case "Stack":
return { name: 'stack_detail', params: { name: e.code } }
case "Config":
return { name: 'config_detail', params: { id: e.code } }
case "Secret":
return { name: 'secret_detail', params: { id: e.code } }
return renderLink({ name: e.type.toLowerCase() + '_detail', params: { id: e.args.id } }, e.args.name)
case "Network":
case "Service":
case "Stack":
return renderLink({ name: e.type.toLowerCase() + '_detail', params: { name: e.args.name } }, e.args.name)
case "Image":
return { name: 'image_detail', params: { node: '-', id: e.code } }
if (e.args.id) {
return renderLink({ name: 'image_detail', params: { node: e.args.node || '-', id: e.args.id } }, e.args.id)
} else {
return renderLink({ name: 'image_list' }, t('objects.image'))
}
case "Container":
return { name: 'container_detail', params: { node: '-', id: e.code } }
if (e.args.id) {
return renderLink({ name: 'container_detail', params: { node: e.args.node || '-', id: e.args.id } }, e.args.name)
} else {
return renderLink({ name: 'container_list' }, t('objects.container'))
}
case "Volume":
return { name: 'volume_detail', params: { node: '-', name: e.code } }
if (e.args.name) {
return renderLink({ name: 'volume_detail', params: { node: e.args.node || '-', name: e.args.name } }, e.args.name)
} else {
return renderLink({ name: 'volume_list' }, t('objects.volume'))
}
case "Setting":
return renderLink({ name: 'setting' }, t('objects.setting'))
}
return null
}

View File

@@ -23,13 +23,19 @@
<x-description-item :label="t('fields.url')">{{ model.url }}</x-description-item>
<x-description-item :label="t('fields.login_name')">{{ model.username }}</x-description-item>
<x-description-item :label="t('fields.created_by')">
<x-anchor :url="`/system/users/${model.createdBy?.id}`">{{ model.createdBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.createdBy?.id } }"
v-if="model.createdBy?.id"
>{{ model.createdBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item>
<x-description-item :label="t('fields.updated_by')">
<x-anchor :url="`/system/users/${model.updatedBy?.id}`">{{ model.updatedBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.updatedBy?.id } }"
v-if="model.updatedBy?.id"
>{{ model.updatedBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />

View File

@@ -111,7 +111,7 @@ const rules: any = {
const form = ref();
const { submit, submiting } = useForm(form, () => roleApi.save(model.value), () => {
window.message.info(t('texts.action_success'));
router.push("/system/roles")
router.push({ name: 'role_list' })
})
function checkGroup(key: string, checked: boolean = true) {

View File

@@ -29,7 +29,7 @@
<tbody>
<tr v-for="(r, index) of model.roles" :key="r.id">
<td>
<x-anchor :url="`/system/roles/${r.id}`">{{ r.id }}</x-anchor>
<x-anchor :url="{ name: 'role_detail', params: { id: r.id } }">{{ r.id }}</x-anchor>
</td>
<td>{{ r.name }}</td>
<td>{{ r.desc }}</td>

View File

@@ -22,24 +22,30 @@
<x-description-item :label="t('fields.name')">{{ model.name }}</x-description-item>
<x-description-item :span="2" :label="t('fields.desc')">{{ model.desc }}</x-description-item>
<x-description-item :label="t('fields.created_by')">
<x-anchor :url="`/system/users/${model.createdBy?.id}`">{{ model.createdBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.createdBy?.id } }"
v-if="model.createdBy?.id"
>{{ model.createdBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item>
<x-description-item :label="t('fields.updated_by')">
<x-anchor :url="`/system/users/${model.updatedBy?.id}`">{{ model.updatedBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.updatedBy?.id } }"
v-if="model.updatedBy?.id"
>{{ model.updatedBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />
</x-description-item>
</x-description>
<x-panel :title="t('fields.perms')">
<n-grid cols="1 640:2 960:3 1440:4" x-gap="6" y-gap="6">
<n-gi span="1" v-for="g in ps">
<x-pair-tag type="warning" :label="g.group" :value="g.items" />
</n-gi>
</n-grid>
<n-grid cols="1 640:2 960:3 1440:4" x-gap="6" y-gap="6">
<n-gi span="1" v-for="g in ps">
<x-pair-tag type="warning" :label="g.group" :value="g.items" />
</n-gi>
</n-grid>
</x-panel>
</n-space>
</template>

View File

@@ -70,7 +70,7 @@
<n-dynamic-input v-model:value="model.labels" #="{ index, value }" :on-create="newLabel">
<n-input :placeholder="t('fields.name')" v-model:value="value.name" />
<div style="height: 34px; line-height: 34px; margin: 0 8px">=</div>
<n-input :placeholder="t('fields.name')" v-model:value="value.value" />
<n-input :placeholder="t('fields.value')" v-model:value="value.value" />
</n-dynamic-input>
</n-form-item-gi>
</n-grid>

View File

@@ -21,13 +21,19 @@
<x-description :label-width="90">
<x-description-item :label="t('fields.name')" :span="2">{{ model.name }}</x-description-item>
<x-description-item :label="t('fields.created_by')">
<x-anchor :url="`/system/users/${model.createdBy?.id}`">{{ model.createdBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.createdBy?.id } }"
v-if="model.createdBy?.id"
>{{ model.createdBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item>
<x-description-item :label="t('fields.updated_by')">
<x-anchor :url="`/system/users/${model.updatedBy?.id}`">{{ model.updatedBy?.name }}</x-anchor>
<x-anchor
:url="{ name: 'user_detail', params: { id: model.updatedBy?.id } }"
v-if="model.updatedBy?.id"
>{{ model.updatedBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />

View File

@@ -1,7 +1,7 @@
<template>
<x-page-header :subtitle="user.id">
<template #action>
<n-button secondary size="small" @click="$router.push('/system/users')">
<n-button secondary size="small" @click="$router.push({ name: 'user_list' })">
<template #icon>
<n-icon>
<back-icon />
@@ -18,16 +18,28 @@
<n-input :placeholder="t('fields.username')" v-model:value="user.name" />
</n-form-item-gi>
<n-form-item-gi :label="t('fields.login_name')" path="loginName">
<n-input :placeholder="t('fields.login_name')" v-model:value="user.loginName" />
<n-input :placeholder="t('fields.login_name')" v-model:value="user.loginName" />
</n-form-item-gi>
<n-form-item-gi :label="t('fields.password')" path="password" v-if="!user.id">
<n-input type="password" :placeholder="t('fields.password')" v-model:value="user.password" />
<n-input
type="password"
:placeholder="t('fields.password')"
v-model:value="user.password"
/>
</n-form-item-gi>
<n-form-item-gi :label="t('fields.password_confirm')" path="passwordConfirm" v-if="!user.id">
<n-input type="password" :placeholder="t('fields.password_confirm')" v-model:value="user.passwordConfirm" />
<n-form-item-gi
:label="t('fields.password_confirm')"
path="passwordConfirm"
v-if="!user.id"
>
<n-input
type="password"
:placeholder="t('fields.password_confirm')"
v-model:value="user.passwordConfirm"
/>
</n-form-item-gi>
<n-form-item-gi :label="t('fields.email')" path="email">
<n-input :placeholder="t('fields.email')" v-model:value="user.email" />
<n-input :placeholder="t('fields.email')" v-model:value="user.email" />
</n-form-item-gi>
<n-form-item-gi :label="t('fields.admin')" path="admin">
<n-switch v-model:value="user.admin">
@@ -47,7 +59,12 @@
<n-radio key="ldap" value="ldap">LDAP</n-radio>
</n-radio-group>
</n-form-item-gi>
<n-form-item-gi :label="t('objects.role', 2)" span="2" path="roles" v-if="roles && roles.length">
<n-form-item-gi
:label="t('objects.role', 2)"
span="2"
path="roles"
v-if="roles && roles.length"
>
<n-checkbox-group v-model:value="user.roles">
<n-space item-style="display: flex;">
<n-checkbox :value="r.id" :label="r.name" v-for="r of roles" />
@@ -119,7 +136,7 @@ const rules: any = {
const form = ref();
const { submit, submiting } = useForm(form, () => userApi.save(user.value), () => {
window.message.info(t('texts.action_success'));
router.push("/system/users")
router.push({ name: 'user_list' })
})
async function fetchData() {

View File

@@ -1,7 +1,7 @@
<template>
<x-page-header>
<template #action>
<n-button secondary size="small" @click="$router.push('/system/users/new')">
<n-button secondary size="small" @click="$router.push({ name: 'user_new' })">
<template #icon>
<n-icon>
<add-icon />
@@ -13,19 +13,12 @@
</x-page-header>
<n-space class="page-body" vertical :size="12">
<x-tab>
<x-tab-pane href="/system/users" :active="!$route.query.filter">{{ t('fields.all') }}</x-tab-pane>
<x-tab-pane :href="{ name: 'user_list' }" :active="!$route.query.filter">{{ t('fields.all') }}</x-tab-pane>
<x-tab-pane
href="/system/users?filter=admins"
:active="$route.query.filter === 'admins'"
>{{ t('fields.admins') }}</x-tab-pane>
<x-tab-pane
href="/system/users?filter=active"
:active="$route.query.filter === 'active'"
>{{ t('fields.active') }}</x-tab-pane>
<x-tab-pane
href="/system/users?filter=blocked"
:active="$route.query.filter === 'blocked'"
>{{ t('fields.blocked') }}</x-tab-pane>
:href="{ name: 'user_list', query: { filter: tab } }"
:active="$route.query.filter === tab"
v-for="tab in ['admins', 'active', 'blocked']"
>{{ t('fields.' + tab) }}</x-tab-pane>
</x-tab>
<n-space :size="12">
<n-input size="small" v-model:value="args.name" :placeholder="t('fields.name')" clearable />
@@ -84,7 +77,7 @@ const columns = [
{
title: t('fields.id'),
key: "id",
render: (row: User) => renderLink(`/system/users/${row.id}`, row.id),
render: (row: User) => renderLink({ name: 'user_detail', params: { id: row.id } }, row.id),
},
{
title: t('fields.name'),
@@ -124,7 +117,7 @@ const columns = [
row.status ?
{ type: 'warning', text: t('buttons.block'), action: () => setStatus(row, 0), prompt: t('prompts.block'), } :
{ type: 'success', text: t('buttons.enable'), action: () => setStatus(row, 1) },
{ type: 'warning', text: t('buttons.edit'), action: () => router.push(`/system/users/${row.id}/edit`) },
{ type: 'warning', text: t('buttons.edit'), action: () => router.push({ name: 'user_edit', params: { id: row.id } }) },
{ type: 'error', text: t('buttons.delete'), action: () => remove(row, index), prompt: t('prompts.delete') },
])
},

View File

@@ -1,7 +1,7 @@
<template>
<x-page-header :subtitle="model.user.name">
<template #action>
<n-button secondary size="small" @click="$router.push('/system/users')">
<n-button secondary size="small" @click="$router.push({ name: 'user_list' })">
<template #icon>
<n-icon>
<back-icon />
@@ -12,7 +12,7 @@
<n-button
secondary
size="small"
@click="$router.push(`/system/users/${model.user.id}/edit`)"
@click="$router.push({ name: 'user_edit', params: { id: model.user.id } })"
>{{ t('buttons.edit') }}</n-button>
</template>
</x-page-header>
@@ -45,7 +45,8 @@
</x-description-item>
<x-description-item :label="t('fields.created_by')">
<x-anchor
:url="`/system/users/${model.user.createdBy?.id}`"
:url="{ name: 'user_detail', params: { id: model.user.createdBy?.id } }"
v-if="model.user.createdBy?.id"
>{{ model.user.createdBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.created_at')">
@@ -53,7 +54,8 @@
</x-description-item>
<x-description-item :label="t('fields.updated_by')">
<x-anchor
:url="`/system/users/${model.user.updatedBy?.id}`"
:url="{ name: 'user_detail', params: { id: model.user.updatedBy?.id } }"
v-if="model.user.updatedBy?.id"
>{{ model.user.updatedBy?.name }}</x-anchor>
</x-description-item>
<x-description-item :label="t('fields.updated_at')">