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

@ -63,7 +63,7 @@ func (b *containerBiz) Search(node, name, status string, pageIndex, pageSize int
func (b *containerBiz) Delete(node, id, name string, user web.User) (err error) { func (b *containerBiz) Delete(node, id, name string, user web.User) (err error) {
err = b.d.ContainerRemove(context.TODO(), node, id) err = b.d.ContainerRemove(context.TODO(), node, id)
if err == nil { if err == nil {
b.eb.CreateContainer(EventActionDelete, id, name, user) b.eb.CreateContainer(EventActionDelete, node, id, name, user)
} }
return return
} }
@ -92,7 +92,7 @@ func (b *containerBiz) Prune(node string, user web.User) (count int, size uint64
var report types.ContainersPruneReport var report types.ContainersPruneReport
if report, err = b.d.ContainerPrune(context.TODO(), node); err == nil { if report, err = b.d.ContainerPrune(context.TODO(), node); err == nil {
count, size = len(report.ContainersDeleted), report.SpaceReclaimed count, size = len(report.ContainersDeleted), report.SpaceReclaimed
b.eb.CreateContainer(EventActionPrune, "", "", user) b.eb.CreateContainer(EventActionPrune, node, "", "", user)
} }
return return
} }

View File

@ -3,6 +3,7 @@ package biz
import ( import (
"context" "context"
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/log" "github.com/cuigh/auxo/log"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/swirl/dao" "github.com/cuigh/swirl/dao"
@ -12,21 +13,20 @@ import (
type EventType string type EventType string
const ( const (
EventTypeRegistry EventType = "Registry" EventTypeRegistry EventType = "Registry"
EventTypeNode EventType = "Node" EventTypeNode EventType = "Node"
EventTypeNetwork EventType = "Network" EventTypeNetwork EventType = "Network"
EventTypeService EventType = "Service" EventTypeService EventType = "Service"
EventTypeServiceTemplate EventType = "Template" EventTypeStack EventType = "Stack"
EventTypeStack EventType = "Stack" EventTypeConfig EventType = "Config"
EventTypeConfig EventType = "Config" EventTypeSecret EventType = "Secret"
EventTypeSecret EventType = "Secret" EventTypeImage EventType = "Image"
EventTypeImage EventType = "Image" EventTypeContainer EventType = "Container"
EventTypeContainer EventType = "Container" EventTypeVolume EventType = "Volume"
EventTypeVolume EventType = "Volume" EventTypeUser EventType = "User"
EventTypeUser EventType = "User" EventTypeRole EventType = "Role"
EventTypeRole EventType = "Role" EventTypeChart EventType = "Chart"
EventTypeChart EventType = "Chart" EventTypeSetting EventType = "Setting"
EventTypeSetting EventType = "Setting"
) )
type EventAction string type EventAction string
@ -51,13 +51,12 @@ type EventBiz interface {
CreateNode(action EventAction, id, name string, user web.User) CreateNode(action EventAction, id, name string, user web.User)
CreateNetwork(action EventAction, id, name string, user web.User) CreateNetwork(action EventAction, id, name string, user web.User)
CreateService(action EventAction, name string, user web.User) CreateService(action EventAction, name string, user web.User)
CreateTemplate(action EventAction, id, name string, user web.User)
CreateConfig(action EventAction, id, name string, user web.User) CreateConfig(action EventAction, id, name string, user web.User)
CreateSecret(action EventAction, id, name string, user web.User) CreateSecret(action EventAction, id, name string, user web.User)
CreateStack(action EventAction, name string, user web.User) CreateStack(action EventAction, name string, user web.User)
CreateImage(action EventAction, id string, user web.User) CreateImage(action EventAction, node, id string, user web.User)
CreateContainer(action EventAction, id, name string, user web.User) CreateContainer(action EventAction, node, id, name string, user web.User)
CreateVolume(action EventAction, name string, user web.User) CreateVolume(action EventAction, node, name string, user web.User)
CreateUser(action EventAction, id, name string, user web.User) CreateUser(action EventAction, id, name string, user web.User)
CreateRole(action EventAction, id, name string, user web.User) CreateRole(action EventAction, id, name string, user web.User)
CreateChart(action EventAction, id, title string, user web.User) CreateChart(action EventAction, id, title string, user web.User)
@ -76,79 +75,99 @@ func (b *eventBiz) Search(args *dao.EventSearchArgs) (events []*dao.Event, total
return b.d.EventSearch(context.TODO(), args) return b.d.EventSearch(context.TODO(), args)
} }
func (b *eventBiz) create(et EventType, ea EventAction, code, name string, user web.User) { func (b *eventBiz) create(et EventType, ea EventAction, args data.Map, user web.User) {
event := &dao.Event{ event := &dao.Event{
ID: primitive.NewObjectID(), ID: primitive.NewObjectID(),
Type: string(et), Type: string(et),
Action: string(ea), Action: string(ea),
Code: code, Args: args,
Name: name,
UserID: user.ID(), UserID: user.ID(),
Username: user.Name(), Username: user.Name(),
Time: now(), Time: now(),
} }
err := b.d.EventCreate(context.TODO(), event) err := b.d.EventCreate(context.TODO(), event)
if err != nil { if err != nil {
log.Get("event").Errorf("failed to create event `%+v`: %v", event, err) log.Get("event").Errorf("failed to create event `%+v`: %s", event, err)
} }
} }
func (b *eventBiz) CreateRegistry(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateRegistry(action EventAction, id, name string, user web.User) {
b.create(EventTypeRegistry, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeRegistry, action, args, user)
} }
func (b *eventBiz) CreateService(action EventAction, name string, user web.User) { func (b *eventBiz) CreateService(action EventAction, name string, user web.User) {
b.create(EventTypeService, action, name, name, user) args := data.Map{"name": name}
} b.create(EventTypeService, action, args, user)
func (b *eventBiz) CreateTemplate(action EventAction, id, name string, user web.User) {
b.create(EventTypeServiceTemplate, action, id, name, user)
} }
func (b *eventBiz) CreateNetwork(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateNetwork(action EventAction, id, name string, user web.User) {
b.create(EventTypeNetwork, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeNetwork, action, args, user)
} }
func (b *eventBiz) CreateNode(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateNode(action EventAction, id, name string, user web.User) {
b.create(EventTypeNode, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeNode, action, args, user)
} }
func (b *eventBiz) CreateImage(action EventAction, id string, user web.User) { func (b *eventBiz) CreateImage(action EventAction, node, id string, user web.User) {
b.create(EventTypeImage, action, id, "", user) args := data.Map{"node": node}
if id != "" {
args["id"] = id
}
b.create(EventTypeImage, action, args, user)
} }
func (b *eventBiz) CreateContainer(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateContainer(action EventAction, node, id, name string, user web.User) {
b.create(EventTypeContainer, action, id, name, user) args := data.Map{"node": node}
if id != "" {
args["id"] = id
}
if name != "" {
args["name"] = name
}
b.create(EventTypeContainer, action, args, user)
} }
func (b *eventBiz) CreateVolume(action EventAction, name string, user web.User) { func (b *eventBiz) CreateVolume(action EventAction, node, name string, user web.User) {
b.create(EventTypeVolume, action, name, name, user) args := data.Map{"node": node}
if name != "" {
args["name"] = name
}
b.create(EventTypeVolume, action, args, user)
} }
func (b *eventBiz) CreateStack(action EventAction, name string, user web.User) { func (b *eventBiz) CreateStack(action EventAction, name string, user web.User) {
b.create(EventTypeStack, action, name, name, user) args := data.Map{"name": name}
b.create(EventTypeStack, action, args, user)
} }
func (b *eventBiz) CreateSecret(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateSecret(action EventAction, id, name string, user web.User) {
b.create(EventTypeSecret, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeSecret, action, args, user)
} }
func (b *eventBiz) CreateConfig(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateConfig(action EventAction, id, name string, user web.User) {
b.create(EventTypeConfig, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeConfig, action, args, user)
} }
func (b *eventBiz) CreateRole(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateRole(action EventAction, id, name string, user web.User) {
b.create(EventTypeRole, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeRole, action, args, user)
} }
func (b *eventBiz) CreateSetting(action EventAction, user web.User) { func (b *eventBiz) CreateSetting(action EventAction, user web.User) {
b.create(EventTypeSetting, action, "", "Setting", user) b.create(EventTypeSetting, action, nil, user)
} }
func (b *eventBiz) CreateUser(action EventAction, id, name string, user web.User) { func (b *eventBiz) CreateUser(action EventAction, id, name string, user web.User) {
b.create(EventTypeUser, action, id, name, user) args := data.Map{"id": id, "name": name}
b.create(EventTypeUser, action, args, user)
} }
func (b *eventBiz) CreateChart(action EventAction, id, title string, user web.User) { func (b *eventBiz) CreateChart(action EventAction, id, title string, user web.User) {
b.create(EventTypeChart, action, id, title, user) args := data.Map{"id": id, "name": title}
b.create(EventTypeChart, action, args, user)
} }

View File

@ -65,7 +65,7 @@ func (b *imageBiz) Search(node, name string, pageIndex, pageSize int) (images []
func (b *imageBiz) Delete(node, id string, user web.User) (err error) { func (b *imageBiz) Delete(node, id string, user web.User) (err error) {
err = b.d.ImageRemove(context.TODO(), node, id) err = b.d.ImageRemove(context.TODO(), node, id)
if err == nil { if err == nil {
b.eb.CreateImage(EventActionDelete, id, user) b.eb.CreateImage(EventActionDelete, node, id, user)
} }
return return
} }
@ -74,7 +74,7 @@ func (b *imageBiz) Prune(node string, user web.User) (count int, size uint64, er
var report types.ImagesPruneReport var report types.ImagesPruneReport
if report, err = b.d.ImagePrune(context.TODO(), node); err == nil { if report, err = b.d.ImagePrune(context.TODO(), node); err == nil {
count, size = len(report.ImagesDeleted), report.SpaceReclaimed count, size = len(report.ImagesDeleted), report.SpaceReclaimed
b.eb.CreateImage(EventActionPrune, "", user) b.eb.CreateImage(EventActionPrune, node, "", user)
} }
return return
} }

View File

@ -60,7 +60,7 @@ func (b *volumeBiz) Search(node, name string, pageIndex, pageSize int) (volumes
func (b *volumeBiz) Delete(node, name string, user web.User) (err error) { func (b *volumeBiz) Delete(node, name string, user web.User) (err error) {
err = b.d.VolumeRemove(context.TODO(), node, name) err = b.d.VolumeRemove(context.TODO(), node, name)
if err == nil { if err == nil {
b.eb.CreateVolume(EventActionDelete, name, user) b.eb.CreateVolume(EventActionDelete, node, name, user)
} }
return return
} }
@ -79,8 +79,8 @@ func (b *volumeBiz) Create(vol *Volume, user web.User) (err error) {
} }
err = b.d.VolumeCreate(context.TODO(), vol.Node, options) err = b.d.VolumeCreate(context.TODO(), vol.Node, options)
if err != nil { if err == nil {
b.eb.CreateVolume(EventActionDelete, vol.Name, user) b.eb.CreateVolume(EventActionDelete, vol.Node, vol.Name, user)
} }
return return
} }
@ -90,7 +90,7 @@ func (b *volumeBiz) Prune(node string, user web.User) (count int, size uint64, e
report, err = b.d.VolumePrune(context.TODO(), node) report, err = b.d.VolumePrune(context.TODO(), node)
if err == nil { if err == nil {
count, size = len(report.VolumesDeleted), report.SpaceReclaimed count, size = len(report.VolumesDeleted), report.SpaceReclaimed
b.eb.CreateVolume(EventActionPrune, "", user) b.eb.CreateVolume(EventActionPrune, node, "", user)
} }
return return
} }

View File

@ -5,6 +5,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/cuigh/auxo/util/cast"
"github.com/cuigh/swirl/dao" "github.com/cuigh/swirl/dao"
"github.com/cuigh/swirl/misc" "github.com/cuigh/swirl/misc"
) )
@ -21,7 +22,7 @@ func (d *Dao) EventSearch(ctx context.Context, args *dao.EventSearchArgs) (event
match := true match := true
if args.Name != "" { if args.Name != "" {
match = matchAny(args.Name, event.Name) match = event.Args != nil && matchAny(args.Name, cast.ToString(event.Args["name"]))
} }
if match && args.Type != "" { if match && args.Type != "" {
match = event.Type == args.Type match = event.Type == args.Type

View File

@ -140,8 +140,7 @@ type Event struct {
ID primitive.ObjectID `json:"id" bson:"_id"` ID primitive.ObjectID `json:"id" bson:"_id"`
Type string `json:"type" bson:"type"` Type string `json:"type" bson:"type"`
Action string `json:"action" bson:"action"` Action string `json:"action" bson:"action"`
Code string `json:"code" bson:"code"` Args data.Map `json:"args" bson:"args"`
Name string `json:"name" bson:"name"`
UserID string `json:"userId" bson:"user_id"` UserID string `json:"userId" bson:"user_id"`
Username string `json:"username" bson:"username"` Username string `json:"username" bson:"username"`
Time Time `json:"time" bson:"time"` Time Time `json:"time" bson:"time"`

View File

@ -15,7 +15,7 @@ func (d *Dao) EventSearch(ctx context.Context, args *dao.EventSearchArgs) (event
filter["type"] = args.Type filter["type"] = args.Type
} }
if args.Name != "" { if args.Name != "" {
filter["name"] = args.Name filter["args.name"] = args.Name
} }
opts := searchOptions{filter: filter, sorter: bson.M{"_id": -1}, pageIndex: args.PageIndex, pageSize: args.PageSize} opts := searchOptions{filter: filter, sorter: bson.M{"_id": -1}, pageIndex: args.PageIndex, pageSize: args.PageSize}
events = []*dao.Event{} events = []*dao.Event{}

View File

@ -33,7 +33,7 @@ var indexes = map[string][]mongo.IndexModel{
}, },
"event": { "event": {
mongo.IndexModel{Keys: bson.D{{"type", 1}}}, mongo.IndexModel{Keys: bson.D{{"type", 1}}},
mongo.IndexModel{Keys: bson.D{{"name", 1}}}, mongo.IndexModel{Keys: bson.D{{"args.name", 1}}},
}, },
"session": { "session": {
mongo.IndexModel{ mongo.IndexModel{

View File

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

View File

@ -291,236 +291,3 @@ export function createChart(dom: HTMLElement, info: ChartInfo): Chart {
throw new Error('unknown chart type: ' + info.type) 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> <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 <router-link
:to="href" :to="href"
:style="{color: active ? themeVars.primaryColor : themeVars.textColorBase}" :style="{ color: active ? themeVars.primaryColor : themeVars.textColorBase }"
> >
<slot /> <slot />
</router-link> </router-link>
@ -11,16 +11,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useThemeVars } from "naive-ui"; import { useThemeVars } from "naive-ui";
import { RouterLink } from "vue-router"; import { RouterLink, RouteLocationRaw } from "vue-router";
const props = defineProps({ interface Props {
active: Boolean, active?: boolean;
title: String, title?: string;
href: { href: RouteLocationRaw;
type: String, }
required: true,
}, const props = defineProps<Props>()
});
const themeVars = useThemeVars(); const themeVars = useThemeVars();
</script> </script>

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<template> <template>
<x-page-header :subtitle="model.title"> <x-page-header :subtitle="model.title">
<template #action> <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> <template #icon>
<n-icon> <n-icon>
<back-icon /> <back-icon />
@ -12,7 +12,7 @@
<n-button <n-button
secondary secondary
size="small" 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> >{{ t('buttons.edit') }}</n-button>
</template> </template>
</x-page-header> </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.dashboard')">{{ model.dashboard }}</x-description-item>
<x-description-item :label="t('fields.type')">{{ model.type }}</x-description-item> <x-description-item :label="t('fields.type')">{{ model.type }}</x-description-item>
<x-description-item :label="t('fields.created_by')"> <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>
<x-description-item :label="t('fields.created_at')"> <x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_by')"> <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>
<x-description-item :label="t('fields.updated_at')"> <x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />

View File

@ -153,7 +153,6 @@ const columns = [
key: "id", key: "id",
width: 210, width: 210,
fixed: "left" as const, fixed: "left" as const,
// render: (e: Event) => renderLink(`/system/events/${e.id}`, e.id),
}, },
{ {
title: t('fields.type'), title: t('fields.type'),
@ -172,15 +171,12 @@ const columns = [
{ {
title: t('fields.object'), title: t('fields.object'),
key: "name", key: "name",
render(e: Event) { render: renderObject,
const u = url(e)
return u ? renderLink(u, e.name) : e.name
},
}, },
{ {
title: t('fields.operator'), title: t('fields.operator'),
key: "name", 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'), title: t('fields.time'),
@ -190,40 +186,40 @@ const columns = [
]; ];
const { state, pagination, fetchData, changePageSize } = useDataTable(eventApi.search, filter) const { state, pagination, fetchData, changePageSize } = useDataTable(eventApi.search, filter)
function url(e: Event): RouteLocationRaw | null { function renderObject(e: Event) {
if (e.type === 'Setting') {
return { name: 'setting' }
} else if (!e.code) {
return null
}
switch (e.type) { switch (e.type) {
case "User": case "User":
return { name: 'user_detail', params: { id: e.code } }
case "Role": case "Role":
return { name: 'role_detail', params: { id: e.code } }
case "Chart": case "Chart":
return { name: 'chart_detail', params: { id: e.code } }
case "Registry": case "Registry":
return { name: 'registry_detail', params: { id: e.code } }
case "Node": 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": case "Config":
return { name: 'config_detail', params: { id: e.code } }
case "Secret": 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": 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": 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": 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 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.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.login_name')">{{ model.username }}</x-description-item>
<x-description-item :label="t('fields.created_by')"> <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>
<x-description-item :label="t('fields.created_at')"> <x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_by')"> <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>
<x-description-item :label="t('fields.updated_at')"> <x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" /> <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 form = ref();
const { submit, submiting } = useForm(form, () => roleApi.save(model.value), () => { const { submit, submiting } = useForm(form, () => roleApi.save(model.value), () => {
window.message.info(t('texts.action_success')); window.message.info(t('texts.action_success'));
router.push("/system/roles") router.push({ name: 'role_list' })
}) })
function checkGroup(key: string, checked: boolean = true) { function checkGroup(key: string, checked: boolean = true) {

View File

@ -29,7 +29,7 @@
<tbody> <tbody>
<tr v-for="(r, index) of model.roles" :key="r.id"> <tr v-for="(r, index) of model.roles" :key="r.id">
<td> <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>
<td>{{ r.name }}</td> <td>{{ r.name }}</td>
<td>{{ r.desc }}</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 :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 :span="2" :label="t('fields.desc')">{{ model.desc }}</x-description-item>
<x-description-item :label="t('fields.created_by')"> <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>
<x-description-item :label="t('fields.created_at')"> <x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_by')"> <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>
<x-description-item :label="t('fields.updated_at')"> <x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />
</x-description-item> </x-description-item>
</x-description> </x-description>
<x-panel :title="t('fields.perms')"> <x-panel :title="t('fields.perms')">
<n-grid cols="1 640:2 960:3 1440:4" x-gap="6" y-gap="6"> <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"> <n-gi span="1" v-for="g in ps">
<x-pair-tag type="warning" :label="g.group" :value="g.items" /> <x-pair-tag type="warning" :label="g.group" :value="g.items" />
</n-gi> </n-gi>
</n-grid> </n-grid>
</x-panel> </x-panel>
</n-space> </n-space>
</template> </template>

View File

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

View File

@ -21,13 +21,19 @@
<x-description :label-width="90"> <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.name')" :span="2">{{ model.name }}</x-description-item>
<x-description-item :label="t('fields.created_by')"> <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>
<x-description-item :label="t('fields.created_at')"> <x-description-item :label="t('fields.created_at')">
<n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.createdAt" format="y-MM-dd HH:mm:ss" />
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_by')"> <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>
<x-description-item :label="t('fields.updated_at')"> <x-description-item :label="t('fields.updated_at')">
<n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" /> <n-time :time="model.updatedAt" format="y-MM-dd HH:mm:ss" />

View File

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

View File

@ -1,7 +1,7 @@
<template> <template>
<x-page-header> <x-page-header>
<template #action> <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> <template #icon>
<n-icon> <n-icon>
<add-icon /> <add-icon />
@ -13,19 +13,12 @@
</x-page-header> </x-page-header>
<n-space class="page-body" vertical :size="12"> <n-space class="page-body" vertical :size="12">
<x-tab> <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 <x-tab-pane
href="/system/users?filter=admins" :href="{ name: 'user_list', query: { filter: tab } }"
:active="$route.query.filter === 'admins'" :active="$route.query.filter === tab"
>{{ t('fields.admins') }}</x-tab-pane> v-for="tab in ['admins', 'active', 'blocked']"
<x-tab-pane >{{ t('fields.' + tab) }}</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>
</x-tab> </x-tab>
<n-space :size="12"> <n-space :size="12">
<n-input size="small" v-model:value="args.name" :placeholder="t('fields.name')" clearable /> <n-input size="small" v-model:value="args.name" :placeholder="t('fields.name')" clearable />
@ -84,7 +77,7 @@ const columns = [
{ {
title: t('fields.id'), title: t('fields.id'),
key: "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'), title: t('fields.name'),
@ -124,7 +117,7 @@ const columns = [
row.status ? row.status ?
{ type: 'warning', text: t('buttons.block'), action: () => setStatus(row, 0), prompt: t('prompts.block'), } : { 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: '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') }, { type: 'error', text: t('buttons.delete'), action: () => remove(row, index), prompt: t('prompts.delete') },
]) ])
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<x-page-header :subtitle="model.user.name"> <x-page-header :subtitle="model.user.name">
<template #action> <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> <template #icon>
<n-icon> <n-icon>
<back-icon /> <back-icon />
@ -12,7 +12,7 @@
<n-button <n-button
secondary secondary
size="small" 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> >{{ t('buttons.edit') }}</n-button>
</template> </template>
</x-page-header> </x-page-header>
@ -45,7 +45,8 @@
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.created_by')"> <x-description-item :label="t('fields.created_by')">
<x-anchor <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> >{{ model.user.createdBy?.name }}</x-anchor>
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.created_at')"> <x-description-item :label="t('fields.created_at')">
@ -53,7 +54,8 @@
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_by')"> <x-description-item :label="t('fields.updated_by')">
<x-anchor <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> >{{ model.user.updatedBy?.name }}</x-anchor>
</x-description-item> </x-description-item>
<x-description-item :label="t('fields.updated_at')"> <x-description-item :label="t('fields.updated_at')">