Refactor setting module

This commit is contained in:
cuigh 2021-12-23 18:00:14 +08:00
parent bb48beec82
commit 62bbe9254d
9 changed files with 114 additions and 193 deletions

View File

@ -1,10 +1,8 @@
package api package api
import ( import (
"bytes"
"encoding/json" "encoding/json"
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/swirl/biz" "github.com/cuigh/swirl/biz"
) )
@ -18,7 +16,7 @@ type SettingHandler struct {
// NewSetting creates an instance of SettingHandler // NewSetting creates an instance of SettingHandler
func NewSetting(b biz.SettingBiz) *SettingHandler { func NewSetting(b biz.SettingBiz) *SettingHandler {
return &SettingHandler{ return &SettingHandler{
Load: settingLoad(b), Load: settingLoad(b),
Save: settingSave(b), Save: settingSave(b),
} }
} }
@ -42,15 +40,8 @@ func settingSave(b biz.SettingBiz) web.HandlerFunc {
return func(ctx web.Context) (err error) { return func(ctx web.Context) (err error) {
args := &Args{} args := &Args{}
err = ctx.Bind(args) err = ctx.Bind(args)
if err != nil { if err == nil {
return err = b.Save(args.ID, args.Options, ctx.User())
}
options := data.Map{}
d := json.NewDecoder(bytes.NewBuffer(args.Options))
d.UseNumber()
if err = d.Decode(&options); err == nil {
err = b.Save(args.ID, options, ctx.User())
} }
return ajax(ctx, err) return ajax(ctx, err)
} }

View File

@ -1,22 +1,21 @@
package biz package biz
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"strconv"
"time" "time"
"github.com/cuigh/auxo/data" "github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/auxo/util/cast"
"github.com/cuigh/swirl/dao" "github.com/cuigh/swirl/dao"
"github.com/cuigh/swirl/model" "github.com/cuigh/swirl/model"
) )
type SettingBiz interface { type SettingBiz interface {
Find(id string) (options data.Map, err error) Find(id string) (options interface{}, err error)
Load() (options data.Map, err error) Load() (options data.Map, err error)
Save(id string, options data.Map, user web.User) (err error) Save(id string, options interface{}, user web.User) (err error)
} }
func NewSetting(d dao.Interface, eb EventBiz) SettingBiz { func NewSetting(d dao.Interface, eb EventBiz) SettingBiz {
@ -28,17 +27,11 @@ type settingBiz struct {
eb EventBiz eb EventBiz
} }
func (b *settingBiz) Find(id string) (options data.Map, err error) { func (b *settingBiz) Find(id string) (options interface{}, err error) {
var setting *model.Setting var setting *model.Setting
setting, err = b.d.SettingGet(context.TODO(), id) setting, err = b.d.SettingGet(context.TODO(), id)
if err != nil { if err == nil && setting != nil {
return return b.unmarshal(setting.Options)
}
if setting != nil {
options = b.toMap(setting.Options)
} else {
options = make(data.Map)
} }
return return
} }
@ -53,82 +46,45 @@ func (b *settingBiz) Load() (options data.Map, err error) {
options = data.Map{} options = data.Map{}
for _, s := range settings { for _, s := range settings {
options[s.ID] = b.toMap(s.Options) var v interface{}
if v, err = b.unmarshal(s.Options); err != nil {
return
}
options[s.ID] = v
} }
return return
} }
func (b *settingBiz) Save(id string, options data.Map, user web.User) (err error) { func (b *settingBiz) Save(id string, options interface{}, user web.User) (err error) {
setting := &model.Setting{ setting := &model.Setting{
ID: id, ID: id,
Options: b.toOptions(options),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
} }
if user != nil { if user != nil {
setting.UpdatedBy = model.Operator{ID: user.ID(), Name: user.Name()} setting.UpdatedBy = model.Operator{ID: user.ID(), Name: user.Name()}
} }
err = b.d.SettingUpdate(context.TODO(), setting)
setting.Options, err = b.marshal(options)
if err == nil {
err = b.d.SettingUpdate(context.TODO(), setting)
}
if err == nil && user != nil { if err == nil && user != nil {
b.eb.CreateSetting(EventActionUpdate, user) b.eb.CreateSetting(EventActionUpdate, user)
} }
return return
} }
func (b *settingBiz) toOptions(m data.Map) []*model.SettingOption { func (b *settingBiz) marshal(v interface{}) (s string, err error) {
var opts []*model.SettingOption var buf []byte
for k, v := range m { if buf, err = json.Marshal(v); err == nil {
opt := &model.SettingOption{Name: k} s = string(buf)
switch v.(type) {
case bool:
opt.Type = "bool"
opt.Value = strconv.FormatBool(v.(bool))
case json.Number:
opt.Type = "number"
opt.Value = cast.ToString(v)
case string:
opt.Type = "string"
opt.Value = v.(string)
default:
opt.Type = "json"
opt.Value = b.toJSON(v)
}
opts = append(opts, opt)
} }
return opts return
} }
func (b *settingBiz) toMap(options []*model.SettingOption) data.Map { func (b *settingBiz) unmarshal(s string) (v interface{}, err error) {
m := data.Map{} d := json.NewDecoder(bytes.NewBuffer([]byte(s)))
for _, opt := range options { d.UseNumber()
var v interface{} err = d.Decode(&v)
switch opt.Type { return
case "bool":
v = opt.Value == "true"
case "number":
v = cast.ToInt32(opt.Value)
case "string":
v = opt.Value
default:
v = b.fromJSON(opt.Value)
}
m[opt.Name] = v
}
return m
}
func (b *settingBiz) toJSON(v interface{}) string {
d, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(d)
}
func (b *settingBiz) fromJSON(v string) interface{} {
var i interface{}
err := json.Unmarshal([]byte(v), &i)
if err != nil {
panic(err)
}
return i
} }

View File

@ -79,7 +79,7 @@ func newInterface() (i Interface) {
case "bolt": case "bolt":
i, err = bolt.New(misc.Options.DBAddress) i, err = bolt.New(misc.Options.DBAddress)
default: default:
err = errors.New("Unknown database type: " + misc.Options.DBType) err = errors.New("unknown database type: " + misc.Options.DBType)
} }
if err != nil { if err != nil {

17
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"embed" "embed"
"encoding/json"
"io/fs" "io/fs"
"net/http" "net/http"
"strings" "strings"
@ -10,7 +11,7 @@ import (
"github.com/cuigh/auxo/app/container" "github.com/cuigh/auxo/app/container"
"github.com/cuigh/auxo/app/flag" "github.com/cuigh/auxo/app/flag"
_ "github.com/cuigh/auxo/cache/memory" _ "github.com/cuigh/auxo/cache/memory"
"github.com/cuigh/auxo/config" "github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/data/valid" "github.com/cuigh/auxo/data/valid"
"github.com/cuigh/auxo/errors" "github.com/cuigh/auxo/errors"
"github.com/cuigh/auxo/log" "github.com/cuigh/auxo/log"
@ -102,13 +103,15 @@ func initSystem() error {
func loadSetting(sb biz.SettingBiz) *misc.Setting { func loadSetting(sb biz.SettingBiz) *misc.Setting {
var ( var (
err error err error
s = &misc.Setting{} opts data.Map
b []byte
s = &misc.Setting{}
) )
if opts, err = sb.Load(); err == nil {
config.AddSource(sb) if b, err = json.Marshal(opts); err == nil {
if err = config.Load(); err == nil { err = json.Unmarshal(b, s)
err = config.Unmarshal(s) }
} }
if err != nil { if err != nil {
log.Get("misc").Error("failed to load setting: ", err) log.Get("misc").Error("failed to load setting: ", err)

View File

@ -49,28 +49,24 @@ func LoadOptions() (err error) {
// Setting represents the settings of Swirl. // Setting represents the settings of Swirl.
type Setting struct { type Setting struct {
System struct { System struct {
Version string Version string `json:"version"`
} } `json:"system"`
Region struct {
Language string `option:"lang"`
Timezone int32
}
LDAP struct { LDAP struct {
Enabled bool Enabled bool `json:"enabled"`
Address string Address string `json:"address"`
Security int32 // 0, 1, 2 Security int32 `json:"security"` // 0, 1, 2
Authentication string `option:"auth"` // simple, bind Authentication string `json:"auth"` // simple, bind
BindDN string BindDN string `json:"bind_dn"`
BindPassword string `option:"bind_pwd"` // Bind DN password BindPassword string `json:"bind_pwd"` // Bind DN password
BaseDN string // Base search path for users BaseDN string `json:"base_dn"` // Base search path for users
UserDN string // Template for the DN of the user for simple auth UserDN string `json:"user_dn"` // Template for the DN of the user for simple auth
UserFilter string // Search filter for user UserFilter string `json:"user_filter"` // Search filter for user
NameAttr string NameAttr string `json:"name_attr"`
EmailAttr string EmailAttr string `json:"email_attr"`
} } `json:"ldap"`
Metric struct { Metric struct {
Prometheus string Prometheus string `json:"prometheus"`
} } `json:"metric"`
} }
func init() { func init() {

View File

@ -58,16 +58,10 @@ type Operator struct {
// Setting represents the options of swirl. // Setting represents the options of swirl.
type Setting struct { type Setting struct {
ID string `json:"id" bson:"_id"` ID string `json:"id" bson:"_id"`
Options []*SettingOption `json:"options" bson:"options"` Options string `json:"options" bson:"options"`
UpdatedAt time.Time `json:"updatedAt" bson:"updated_at"` UpdatedAt time.Time `json:"updatedAt" bson:"updated_at"`
UpdatedBy Operator `json:"updatedBy" bson:"updated_by"` UpdatedBy Operator `json:"updatedBy" bson:"updated_by"`
}
type SettingOption struct {
Name string `json:"name" bson:"name"`
Value string `json:"value" bson:"value"`
Type string `json:"type" bson:"type"`
} }
type Role struct { type Role struct {

View File

@ -1,14 +1,17 @@
import ajax, { Result } from './ajax' import ajax, { Result } from './ajax'
export interface Setting { export interface Setting {
region: RegionSetting;
ldap: LdapSetting; ldap: LdapSetting;
metric: MetricSetting; metric: MetricSetting;
deploy: DeployOptions;
} }
export interface RegionSetting { export interface DeployOptions {
lang: string; keys: {
timezone: number; name: string;
token: string;
expiry: number;
}[];
} }
export interface LdapSetting { export interface LdapSetting {

View File

@ -1,5 +1,5 @@
<template> <template>
<x-page-header> <x-page-header>
<template #action> <template #action>
<n-button secondary size="small" type="warning" @click="prune"> <n-button secondary size="small" type="warning" @click="prune">
<template #icon> <template #icon>
@ -73,7 +73,16 @@ const columns = [
fixed: "left" as const, fixed: "left" as const,
render: (c: Container) => { render: (c: Container) => {
const node = c.labels?.find(l => l.name === 'com.docker.swarm.node.id') const node = c.labels?.find(l => l.name === 'com.docker.swarm.node.id')
return renderLink({ name: 'container_detail', params: { id: c.id, node: node?.value || '-' } }, c.name) const name = c.name.length > 32 ? c.name.substring(0, 32) + '...' : c.name
return renderLink({ name: 'container_detail', params: { id: c.id, node: node?.value || '-' } }, name)
},
},
{
title: t('objects.service'),
key: "service",
render: (c: Container) => {
const service = c.labels?.find(l => l.name === 'com.docker.swarm.service.name')?.value
return service ? renderLink({ name: 'service_detail', params: { name: service } }, service) : ''
}, },
}, },
{ {

View File

@ -1,36 +1,36 @@
<template> <template>
<x-page-header /> <x-page-header />
<n-space class="page-body" vertical :size="12"> <n-space class="page-body" vertical :size="12">
<x-panel title="Region" divider="bottom" :collapsed="panel !== 'region'" v-if="false"> <x-panel title="Deployment" divider="bottom" :collapsed="panel !== 'deploy'" v-if="false">
<template #action> <template #action>
<n-button <n-button
secondary secondary
strong strong
class="toggle" class="toggle"
size="small" size="small"
@click="togglePanel('region')" @click="togglePanel('deploy')"
>{{ panel === 'region' ? t('buttons.collapse') : t('buttons.expand') }}</n-button> >{{ panel === 'deploy' ? t('buttons.collapse') : t('buttons.expand') }}</n-button>
</template> </template>
<div style="padding: 4px 0 0 12px"> <div style="padding: 4px 0 0 12px">
<n-form <n-form :model="setting" ref="formDeploy" :show-feedback="false">
inline <n-form-item :label="t('fields.keys')" path="deploy.keys">
:model="setting" <n-dynamic-input
ref="formRegion" v-model:value="setting.deploy.keys"
label-placement="left" #="{ index, value }"
:show-feedback="false" :on-create="newKey"
> >
<n-form-item label="Timezone" path="region.timezone"> <n-input-group>
<n-select <n-input :placeholder="t('fields.name')" v-model:value="value.name" />
v-model:value="setting.region.timezone" <n-input :placeholder="t('fields.token')" v-model:value="value.token" />
:options="timezones" <n-date-picker :placeholder="t('fields.expiry')" v-model:value="value.expiry" type="date" clearable style="min-width: 200px"/>
style="width: 240px" </n-input-group>
/> </n-dynamic-input>
</n-form-item> </n-form-item>
</n-form> </n-form>
<n-button <n-button
type="primary" type="primary"
style="margin-top: 12px" style="margin-top: 12px"
@click="() => save('region', setting.region)" @click="() => save('deploy', setting.deploy)"
>{{ t('buttons.save') }}</n-button> >{{ t('buttons.save') }}</n-button>
</div> </div>
</x-panel> </x-panel>
@ -55,10 +55,7 @@
<n-switch v-model:value="setting.ldap.enabled" /> <n-switch v-model:value="setting.ldap.enabled" />
</n-form-item> </n-form-item>
<n-form-item :label="t('fields.address')" path="ldap.address" label-align="right"> <n-form-item :label="t('fields.address')" path="ldap.address" label-align="right">
<n-input <n-input :placeholder="t('tips.ldap_address')" v-model:value="setting.ldap.address" />
:placeholder="t('tips.ldap_address')"
v-model:value="setting.ldap.address"
/>
</n-form-item> </n-form-item>
<n-form-item :label="t('fields.security')" path="ldap.security"> <n-form-item :label="t('fields.security')" path="ldap.security">
<n-radio-group v-model:value="setting.ldap.security"> <n-radio-group v-model:value="setting.ldap.security">
@ -79,10 +76,7 @@
label-align="right" label-align="right"
v-show="setting.ldap.auth === 'simple'" v-show="setting.ldap.auth === 'simple'"
> >
<n-input <n-input :placeholder="t('tips.ldap_user_dn')" v-model:value="setting.ldap.user_dn" />
:placeholder="t('tips.ldap_user_dn')"
v-model:value="setting.ldap.user_dn"
/>
</n-form-item> </n-form-item>
<n-form-item <n-form-item
:label="t('fields.bind_dn')" :label="t('fields.bind_dn')"
@ -93,7 +87,7 @@
<n-grid :cols="2" :x-gap="24"> <n-grid :cols="2" :x-gap="24">
<n-form-item-gi path="ldap.bind_dn"> <n-form-item-gi path="ldap.bind_dn">
<n-input-group> <n-input-group>
<n-input-group-label style="min-width: 60px">{{ t('fields.dn')}}</n-input-group-label> <n-input-group-label style="min-width: 60px">{{ t('fields.dn') }}</n-input-group-label>
<n-input <n-input
:placeholder="t('tips.ldap_bind_dn')" :placeholder="t('tips.ldap_bind_dn')"
v-model:value="setting.ldap.bind_dn" v-model:value="setting.ldap.bind_dn"
@ -102,17 +96,18 @@
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi path="ldap.bind_pwd"> <n-form-item-gi path="ldap.bind_pwd">
<n-input-group> <n-input-group>
<n-input-group-label style="min-width: 60px">{{ t('fields.password')}}</n-input-group-label> <n-input-group-label style="min-width: 60px">{{ t('fields.password') }}</n-input-group-label>
<n-input type="password" :placeholder="t('tips.ldap_bind_pwd')" v-model:value="setting.ldap.bind_pwd" /> <n-input
type="password"
:placeholder="t('tips.ldap_bind_pwd')"
v-model:value="setting.ldap.bind_pwd"
/>
</n-input-group> </n-input-group>
</n-form-item-gi> </n-form-item-gi>
</n-grid> </n-grid>
</n-form-item> </n-form-item>
<n-form-item :label="t('fields.base_dn')" path="ldap.base_dn" label-align="right"> <n-form-item :label="t('fields.base_dn')" path="ldap.base_dn" label-align="right">
<n-input <n-input :placeholder="t('tips.ldap_base_dn')" v-model:value="setting.ldap.base_dn" />
:placeholder="t('tips.ldap_base_dn')"
v-model:value="setting.ldap.base_dn"
/>
</n-form-item> </n-form-item>
<n-form-item :label="t('fields.user_filter')" path="ldap.user_filter" label-align="right"> <n-form-item :label="t('fields.user_filter')" path="ldap.user_filter" label-align="right">
<n-input <n-input
@ -160,10 +155,7 @@
style="padding: 4px 0 0 12px" style="padding: 4px 0 0 12px"
> >
<n-form-item label="Prometheus" path="metric.prometheus" label-align="right"> <n-form-item label="Prometheus" path="metric.prometheus" label-align="right">
<n-input <n-input :placeholder="t('tips.prometheus')" v-model:value="setting.metric.prometheus" />
:placeholder="t('tips.prometheus')"
v-model:value="setting.metric.prometheus"
/>
</n-form-item> </n-form-item>
<n-button <n-button
type="primary" type="primary"
@ -189,9 +181,10 @@ import {
NFormItemGi, NFormItemGi,
NRadioGroup, NRadioGroup,
NRadio, NRadio,
NSelect, NDynamicInput,
NSwitch, NSwitch,
NAlert, NAlert,
NDatePicker,
} from "naive-ui"; } from "naive-ui";
import XPageHeader from "@/components/PageHeader.vue"; import XPageHeader from "@/components/PageHeader.vue";
import XPanel from "@/components/Panel.vue"; import XPanel from "@/components/Panel.vue";
@ -201,41 +194,13 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const setting = ref({ const setting = ref({
region: {
timezone: 8,
},
ldap: { ldap: {
security: 0, security: 0,
auth: 'simple', auth: 'simple',
}, },
metric: {}, metric: {},
deploy: {},
} as Setting); } as Setting);
const timezones = [
{ label: 'UTC-11(Pacific/Midway)', value: -11 },
{ label: 'UTC-10(Pacific/Honolulu)', value: -10 },
{ label: 'UTC-9(America/Anchorage)', value: -9 },
{ label: 'UTC-8(America/Los_Angeles)', value: -8 },
{ label: 'UTC-7(America/Phoenix)', value: -7 },
{ label: 'UTC-6(America/Regina)', value: -6 },
{ label: 'UTC-5(America/Panama)', value: -5 },
{ label: 'UTC-4(America/Asuncion)', value: -4 },
{ label: 'UTC-3(America/Bahia)', value: -3 },
{ label: 'UTC-2(America/Noronha)', value: -2 },
{ label: 'UTC-1(Atlantic/Cape_Verde)', value: -1 },
{ label: 'UTC(Europe/London)', value: 0 },
{ label: 'UTC+1(Africa/Algiers)', value: 1 },
{ label: 'UTC+2(Africa/Cairo)', value: 2 },
{ label: 'UTC+3(Europe/Moscow)', value: 3 },
{ label: 'UTC+4(Indian/Mahe)', value: 4 },
{ label: 'UTC+5(Asia/Karachi)', value: 5 },
{ label: 'UTC+6(Asia/Almaty)', value: 6 },
{ label: 'UTC+7(Asia/Bangkok)', value: 7 },
{ label: 'UTC+8(Asia/Shanghai)', value: 8 },
{ label: 'UTC+9(Asia/Tokyo)', value: 9 },
{ label: 'UTC+10(Australia/Sydney)', value: 10 },
{ label: 'UTC+11(Pacific/Efate)', value: 11 },
{ label: 'UTC+12(Asia/Anadyr)', value: 12 },
]
const panel = ref('') const panel = ref('')
function togglePanel(name: string) { function togglePanel(name: string) {
@ -246,6 +211,10 @@ function togglePanel(name: string) {
} }
} }
function newKey() {
return { name: '', token: '', expiry: undefined }
}
async function save(id: string, options: any) { async function save(id: string, options: any) {
await settingApi.save(id, options) await settingApi.save(id, options)
window.message.info(t('texts.action_success')); window.message.info(t('texts.action_success'));