mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
feat(site): save sync node ids
This commit is contained in:
parent
aa556767f2
commit
6c137e5229
21 changed files with 381 additions and 327 deletions
0
.db
0
.db
|
@ -6,6 +6,7 @@ func InitRouter(r *gin.RouterGroup) {
|
|||
r.GET("domains", GetSiteList)
|
||||
r.GET("domains/:name", GetSite)
|
||||
r.POST("domains/:name", SaveSite)
|
||||
r.PUT("domains", BatchUpdateSites)
|
||||
r.POST("domains/:name/enable", EnableSite)
|
||||
r.POST("domains/:name/disable", DisableSite)
|
||||
r.POST("domains/:name/advance", DomainEditByAdvancedMode)
|
||||
|
|
|
@ -9,7 +9,9 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/uozi-tech/cosy"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
"gorm.io/gorm/clause"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
@ -128,6 +130,7 @@ func SaveSite(c *gin.Context) {
|
|||
Name string `json:"name" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
SiteCategoryID uint64 `json:"site_category_id"`
|
||||
SyncNodeIDs []uint64 `json:"sync_node_ids"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
}
|
||||
|
||||
|
@ -152,7 +155,12 @@ func SaveSite(c *gin.Context) {
|
|||
enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
|
||||
s := query.Site
|
||||
|
||||
_, err = s.Where(s.Path.Eq(path)).Update(s.SiteCategoryID, json.SiteCategoryID)
|
||||
_, err = s.Where(s.Path.Eq(path)).
|
||||
Select(s.SiteCategoryID, s.SyncNodeIDs).
|
||||
Updates(&model.Site{
|
||||
SiteCategoryID: json.SiteCategoryID,
|
||||
SyncNodeIDs: json.SyncNodeIDs,
|
||||
})
|
||||
if err != nil {
|
||||
api.ErrHandler(c, err)
|
||||
return
|
||||
|
@ -336,3 +344,29 @@ func DeleteSite(c *gin.Context) {
|
|||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func BatchUpdateSites(c *gin.Context) {
|
||||
cosy.Core[model.Site](c).SetValidRules(gin.H{
|
||||
"site_category_id": "required",
|
||||
}).SetItemKey("path").
|
||||
BeforeExecuteHook(func(ctx *cosy.Ctx[model.Site]) {
|
||||
effectedPath := make([]string, len(ctx.BatchEffectedIDs))
|
||||
var sites []*model.Site
|
||||
for i, name := range ctx.BatchEffectedIDs {
|
||||
path := nginx.GetConfPath("sites-available", name)
|
||||
effectedPath[i] = path
|
||||
sites = append(sites, &model.Site{
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
s := query.Site
|
||||
err := s.Clauses(clause.OnConflict{
|
||||
DoNothing: true,
|
||||
}).Create(sites...)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(err)
|
||||
return
|
||||
}
|
||||
ctx.BatchEffectedIDs = effectedPath
|
||||
}).BatchModify()
|
||||
}
|
|
@ -1,95 +1,95 @@
|
|||
export default {
|
||||
globals: {
|
||||
$gettext: true,
|
||||
$ngettext: true,
|
||||
$npgettext: true,
|
||||
$pgettext: true,
|
||||
Component: true,
|
||||
ComponentPublicInstance: true,
|
||||
ComputedRef: true,
|
||||
DirectiveBinding: true,
|
||||
EffectScope: true,
|
||||
ExtractDefaultPropTypes: true,
|
||||
ExtractPropTypes: true,
|
||||
ExtractPublicPropTypes: true,
|
||||
InjectionKey: true,
|
||||
MaybeRef: true,
|
||||
MaybeRefOrGetter: true,
|
||||
PropType: true,
|
||||
Ref: true,
|
||||
VNode: true,
|
||||
WritableComputedRef: true,
|
||||
acceptHMRUpdate: true,
|
||||
computed: true,
|
||||
createApp: true,
|
||||
createPinia: true,
|
||||
customRef: true,
|
||||
defineAsyncComponent: true,
|
||||
defineComponent: true,
|
||||
defineStore: true,
|
||||
effectScope: true,
|
||||
getActivePinia: true,
|
||||
getCurrentInstance: true,
|
||||
getCurrentScope: true,
|
||||
h: true,
|
||||
inject: true,
|
||||
isProxy: true,
|
||||
isReactive: true,
|
||||
isReadonly: true,
|
||||
isRef: true,
|
||||
mapActions: true,
|
||||
mapGetters: true,
|
||||
mapState: true,
|
||||
mapStores: true,
|
||||
mapWritableState: true,
|
||||
markRaw: true,
|
||||
nextTick: true,
|
||||
onActivated: true,
|
||||
onBeforeMount: true,
|
||||
onBeforeRouteLeave: true,
|
||||
onBeforeRouteUpdate: true,
|
||||
onBeforeUnmount: true,
|
||||
onBeforeUpdate: true,
|
||||
onDeactivated: true,
|
||||
onErrorCaptured: true,
|
||||
onMounted: true,
|
||||
onRenderTracked: true,
|
||||
onRenderTriggered: true,
|
||||
onScopeDispose: true,
|
||||
onServerPrefetch: true,
|
||||
onUnmounted: true,
|
||||
onUpdated: true,
|
||||
onWatcherCleanup: true,
|
||||
provide: true,
|
||||
reactive: true,
|
||||
readonly: true,
|
||||
ref: true,
|
||||
resolveComponent: true,
|
||||
setActivePinia: true,
|
||||
setMapStoreSuffix: true,
|
||||
shallowReactive: true,
|
||||
shallowReadonly: true,
|
||||
shallowRef: true,
|
||||
storeToRefs: true,
|
||||
toRaw: true,
|
||||
toRef: true,
|
||||
toRefs: true,
|
||||
toValue: true,
|
||||
triggerRef: true,
|
||||
unref: true,
|
||||
useAttrs: true,
|
||||
useCssModule: true,
|
||||
useCssVars: true,
|
||||
useId: true,
|
||||
useLink: true,
|
||||
useModel: true,
|
||||
useRoute: true,
|
||||
useRouter: true,
|
||||
useSlots: true,
|
||||
useTemplateRef: true,
|
||||
watch: true,
|
||||
watchEffect: true,
|
||||
watchPostEffect: true,
|
||||
watchSyncEffect: true,
|
||||
},
|
||||
"globals": {
|
||||
"$gettext": true,
|
||||
"$ngettext": true,
|
||||
"$npgettext": true,
|
||||
"$pgettext": true,
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"createPinia": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"effectScope": true,
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
"mapState": true,
|
||||
"mapStores": true,
|
||||
"mapWritableState": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeRouteLeave": true,
|
||||
"onBeforeRouteUpdate": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"onWatcherCleanup": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useId": true,
|
||||
"useLink": true,
|
||||
"useModel": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"useTemplateRef": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
|
|
2
app/components.d.ts
vendored
2
app/components.d.ts
vendored
|
@ -49,6 +49,8 @@ declare module 'vue' {
|
|||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
AResult: typeof import('ant-design-vue/es')['Result']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
|
|
|
@ -5,7 +5,7 @@ import autoImport from './.eslint-auto-import.mjs'
|
|||
export default createConfig(
|
||||
{
|
||||
stylistic: true,
|
||||
ignores: ['**/version.json', 'tsconfig.json', 'tsconfig.node.json'],
|
||||
ignores: ['**/version.json', 'tsconfig.json', 'tsconfig.node.json', '.eslint-auto-import.mjs'],
|
||||
languageOptions: {
|
||||
globals: autoImport.globals,
|
||||
},
|
||||
|
|
|
@ -19,6 +19,7 @@ export interface Site {
|
|||
cert_info?: Record<number, CertificateInfo[]>
|
||||
site_category_id: number
|
||||
site_category?: SiteCategory
|
||||
sync_node_ids: number[]
|
||||
}
|
||||
|
||||
export interface AutoCertRequest {
|
||||
|
|
|
@ -1,46 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import type Curd from '@/api/curd'
|
||||
import type { Column } from '@/components/StdDesign/types'
|
||||
import { getPithyColumns } from '@/components/StdDesign/StdDataDisplay/methods/columns'
|
||||
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const props = defineProps<{
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
api: (ids: number[], data: any) => Promise<void>
|
||||
api: Curd<any>
|
||||
beforeSave?: () => Promise<void>
|
||||
columns: Column[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['onSave'])
|
||||
const emit = defineEmits(['save'])
|
||||
|
||||
const batchColumns = ref([])
|
||||
const batchColumns = ref<Column[]>([])
|
||||
const selectedRowKeys = ref<(number | string)[]>([])
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const selectedRows = ref<any[]>([])
|
||||
|
||||
const visible = ref(false)
|
||||
const data = ref({})
|
||||
const error = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
const selectedRowKeys = ref([])
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
function showModal(c: any, rowKeys: any) {
|
||||
function showModal(c: Column[], rowKeys: (number | string)[], rows: any[]) {
|
||||
data.value = {}
|
||||
visible.value = true
|
||||
selectedRowKeys.value = rowKeys
|
||||
batchColumns.value = c
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showModal,
|
||||
})
|
||||
|
||||
const data = reactive({})
|
||||
const error = reactive({})
|
||||
const loading = ref(false)
|
||||
|
||||
async function ok() {
|
||||
loading.value = true
|
||||
|
||||
await props.beforeSave?.()
|
||||
|
||||
await props.api(selectedRowKeys.value, data).then(async () => {
|
||||
await props.api.batch_save(selectedRowKeys.value, data.value)
|
||||
.then(async () => {
|
||||
message.success($gettext('Save successfully'))
|
||||
emit('onSave')
|
||||
}).catch(e => {
|
||||
emit('save')
|
||||
visible.value = false
|
||||
})
|
||||
.catch(e => {
|
||||
error.value = e.errors
|
||||
message.error($gettext(e?.message) ?? $gettext('Server error'))
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
@ -52,23 +64,31 @@ async function ok() {
|
|||
class="std-curd-edit-modal"
|
||||
:mask="false"
|
||||
:title="$gettext('Batch Modify')"
|
||||
:cancel-text="$gettext('Cancel')"
|
||||
:ok-text="$gettext('OK')"
|
||||
:cancel-text="$gettext('No')"
|
||||
:ok-text="$gettext('Save')"
|
||||
:confirm-loading="loading"
|
||||
:width="600"
|
||||
destroy-on-close
|
||||
@ok="ok"
|
||||
>
|
||||
<p>{{ $gettext('Belows are selected items that you want to batch modify') }}</p>
|
||||
<ATable
|
||||
class="mb-4"
|
||||
size="small"
|
||||
:columns="getPithyColumns(columns)"
|
||||
:data-source="selectedRows"
|
||||
:pagination="{ showSizeChanger: false, pageSize: 5, size: 'small' }"
|
||||
/>
|
||||
|
||||
<p>{{ $gettext('Leave blank if do not want to modify') }}</p>
|
||||
<StdDataEntry
|
||||
:data-list="batchColumns"
|
||||
:data-source="data"
|
||||
:error="error"
|
||||
:errors="error"
|
||||
/>
|
||||
|
||||
<slot name="extra" />
|
||||
</AModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts" generic="T=any">
|
||||
import type { StdTableSlots } from '@/components/StdDesign/StdDataDisplay/types'
|
||||
import type { Column } from '@/components/StdDesign/types'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { StdTableProps } from './StdTable.vue'
|
||||
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
|
||||
import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
|
||||
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
@ -14,7 +14,7 @@ export interface StdCurdProps<T> extends StdTableProps<T> {
|
|||
modalMask?: boolean
|
||||
exportExcel?: boolean
|
||||
importExcel?: boolean
|
||||
disableTrash?: boolean
|
||||
|
||||
disableAdd?: boolean
|
||||
onClickAdd?: () => void
|
||||
|
||||
|
@ -24,6 +24,10 @@ export interface StdCurdProps<T> extends StdTableProps<T> {
|
|||
}
|
||||
|
||||
const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
|
||||
|
||||
const selectedRowKeys = ref<(string | number)[]>([])
|
||||
const selectedRows: Ref<T[]> = ref([])
|
||||
|
||||
const visible = ref(false)
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const data: any = reactive({ id: null })
|
||||
|
@ -61,24 +65,13 @@ function add(preset: any = undefined) {
|
|||
if (preset)
|
||||
Object.assign(data, preset)
|
||||
|
||||
clear_error()
|
||||
clearError()
|
||||
visible.value = true
|
||||
editMode.value = 'create'
|
||||
modifyMode.value = true
|
||||
}
|
||||
|
||||
const table = ref()
|
||||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const selectedRows = defineModel<any[]>('selectedRows', {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
})
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
const getParams = reactive({
|
||||
trash: false,
|
||||
|
@ -101,20 +94,23 @@ defineExpose({
|
|||
setParams,
|
||||
})
|
||||
|
||||
function clear_error() {
|
||||
function clearError() {
|
||||
Object.keys(error).forEach(v => {
|
||||
delete error[v]
|
||||
})
|
||||
}
|
||||
|
||||
const stdEntryRef = ref()
|
||||
const stdEntryRef = useTemplateRef('stdEntryRef')
|
||||
|
||||
async function ok() {
|
||||
if (!stdEntryRef.value)
|
||||
return
|
||||
|
||||
const { formRef } = stdEntryRef.value
|
||||
|
||||
clear_error()
|
||||
clearError()
|
||||
try {
|
||||
await formRef.validateFields()
|
||||
await formRef?.validateFields()
|
||||
props?.beforeSave?.(data)
|
||||
props
|
||||
.api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } }).then(r => {
|
||||
|
@ -135,7 +131,7 @@ async function ok() {
|
|||
function cancel() {
|
||||
visible.value = false
|
||||
|
||||
clear_error()
|
||||
clearError()
|
||||
|
||||
if (shouldRefetchList.value) {
|
||||
get_list()
|
||||
|
@ -159,7 +155,6 @@ function view(id: number | string) {
|
|||
get(id).then(() => {
|
||||
visible.value = true
|
||||
modifyMode.value = false
|
||||
editMode.value = 'modify'
|
||||
}).catch(e => {
|
||||
message.error($gettext(e?.message ?? 'Server error'), 5)
|
||||
})
|
||||
|
@ -183,6 +178,17 @@ const modalTitle = computed(() => {
|
|||
})
|
||||
|
||||
const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
||||
|
||||
const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
|
||||
|
||||
async function handleClickBatchEdit(batchColumns: Column[]) {
|
||||
stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
|
||||
}
|
||||
|
||||
function handleBatchUpdated() {
|
||||
table.value?.get_list()
|
||||
table.value?.resetSelection()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -202,7 +208,7 @@ const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
|||
@click="add"
|
||||
>{{ $gettext('Add') }}</a>
|
||||
<slot name="extra" />
|
||||
<template v-if="!disableDelete && !disableTrash">
|
||||
<template v-if="!disableDelete">
|
||||
<a
|
||||
v-if="!getParams.trash"
|
||||
@click="getParams.trash = true"
|
||||
|
@ -219,21 +225,23 @@ const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
|||
</ASpace>
|
||||
</template>
|
||||
|
||||
<slot name="beforeTable" />
|
||||
<StdTable
|
||||
ref="table"
|
||||
v-model:selected-row-keys="selectedRowKeys"
|
||||
v-model:selected-rows="selectedRows"
|
||||
v-bind="{
|
||||
...props,
|
||||
getParams,
|
||||
overwriteParams: localOverwriteParams,
|
||||
}"
|
||||
v-model:selected-row-keys="selectedRowKeys"
|
||||
v-model:selected-rows="selectedRows"
|
||||
@click-edit="edit"
|
||||
@click-view="view"
|
||||
@selected="onSelect"
|
||||
@click-batch-modify="handleClickBatchEdit"
|
||||
>
|
||||
<template
|
||||
v-for="(_, key) in ($slots as unknown as StdTableSlots)"
|
||||
v-for="(_, key) in $slots"
|
||||
:key="key"
|
||||
#[key]="slotProps"
|
||||
>
|
||||
|
@ -295,10 +303,17 @@ const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
|||
|
||||
<StdCurdDetail
|
||||
v-else
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:columns
|
||||
:data
|
||||
/>
|
||||
</AModal>
|
||||
|
||||
<StdBatchEdit
|
||||
ref="stdBatchEditRef"
|
||||
:api
|
||||
:columns
|
||||
@save="handleBatchUpdated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ import type { FilterValue } from 'ant-design-vue/es/table/interface'
|
|||
import type { SorterResult, TablePaginationConfig } from 'ant-design-vue/lib/table/interface'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { RouteParams } from 'vue-router'
|
||||
import { getPithyColumns } from '@/components/StdDesign/StdDataDisplay/methods/columns'
|
||||
import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
|
||||
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
||||
import { HolderOutlined } from '@ant-design/icons-vue'
|
||||
import { watchPausable } from '@vueuse/core'
|
||||
import { message } from 'ant-design-vue'
|
||||
import _ from 'lodash'
|
||||
import StdPagination from './StdPagination.vue'
|
||||
|
@ -20,6 +22,7 @@ export interface StdTableProps<T = any> {
|
|||
title?: string
|
||||
mode?: string
|
||||
rowKey?: string
|
||||
|
||||
api: Curd<T>
|
||||
columns: Column[]
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
|
@ -48,7 +51,20 @@ const props = withDefaults(defineProps<StdTableProps<T>>(), {
|
|||
rowKey: 'id',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['clickEdit', 'clickView', 'clickBatchModify', 'update:selectedRowKeys'])
|
||||
const emit = defineEmits([
|
||||
'clickEdit',
|
||||
'clickView',
|
||||
'clickBatchModify',
|
||||
])
|
||||
|
||||
const selectedRowKeys = defineModel<(number | string)[]>('selectedRowKeys', {
|
||||
default: () => reactive([]),
|
||||
})
|
||||
|
||||
const selectedRows = defineModel<T[]>('selectedRows', {
|
||||
default: () => reactive([]),
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const dataSource: Ref<T[]> = ref([])
|
||||
|
@ -93,17 +109,6 @@ const params = reactive({
|
|||
...props.getParams,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const selectedRows = defineModel<any[]>('selectedRows', {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
selectedRows.value.forEach(v => {
|
||||
selectedRecords.value[v[props.rowKey]] = v
|
||||
|
@ -122,7 +127,9 @@ const searchColumns = computed(() => {
|
|||
})
|
||||
}
|
||||
|
||||
else { _searchColumns.push({ ...column }) }
|
||||
else {
|
||||
_searchColumns.push({ ...column })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -130,11 +137,8 @@ const searchColumns = computed(() => {
|
|||
})
|
||||
|
||||
const pithyColumns = computed<Column[]>(() => {
|
||||
if (props.pithy) {
|
||||
return props.columns?.filter(c => {
|
||||
return c.pithy === true && !c.hiddenInTable
|
||||
})
|
||||
}
|
||||
if (props.pithy)
|
||||
return getPithyColumns(props.columns)
|
||||
|
||||
return props.columns?.filter(c => {
|
||||
return !c.hiddenInTable
|
||||
|
@ -142,19 +146,12 @@ const pithyColumns = computed<Column[]>(() => {
|
|||
})
|
||||
|
||||
const batchColumns = computed(() => {
|
||||
const batch: Column[] = []
|
||||
|
||||
props.columns?.forEach(column => {
|
||||
if (column.batch)
|
||||
batch.push(column)
|
||||
})
|
||||
|
||||
return batch
|
||||
return props.columns?.filter(column => column.batch) || []
|
||||
})
|
||||
|
||||
const get_list = _.debounce(_get_list, 100, {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
leading: true,
|
||||
trailing: false,
|
||||
})
|
||||
|
||||
const filterParams = reactive({})
|
||||
|
@ -184,15 +181,13 @@ onMounted(() => {
|
|||
if (props.sortable)
|
||||
initSortable()
|
||||
|
||||
if (!selectedRowKeys.value?.length)
|
||||
selectedRowKeys.value = []
|
||||
|
||||
init.value = true
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
get_list,
|
||||
pagination,
|
||||
resetSelection,
|
||||
})
|
||||
|
||||
function destroy(id: number | string) {
|
||||
|
@ -229,13 +224,17 @@ function buildIndexMap(data: any, level: number = 0, index: number = 0, total: n
|
|||
}
|
||||
}
|
||||
|
||||
async function _get_list(page_num = null, page_size = 20) {
|
||||
async function _get_list(page_num: number | null = null, page_size = 20) {
|
||||
dataSource.value = []
|
||||
loading.value = true
|
||||
if (page_num) {
|
||||
params.page = page_num
|
||||
params.page_size = page_size
|
||||
}
|
||||
else {
|
||||
params.page = 1
|
||||
params.page_size = page_size
|
||||
}
|
||||
props.api?.get_list({ ...params, ...props.overwriteParams }).then(async r => {
|
||||
dataSource.value = r.data
|
||||
rowsKeyIndexMap.value = {}
|
||||
|
@ -245,9 +244,7 @@ async function _get_list(page_num = null, page_size = 20) {
|
|||
if (r.pagination)
|
||||
Object.assign(pagination, r.pagination)
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 200)
|
||||
}).catch(e => {
|
||||
message.error(e?.message ?? $gettext('Server error'))
|
||||
})
|
||||
|
@ -288,7 +285,8 @@ function expandedTable(keys: Key[]) {
|
|||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
|
||||
if (props.selectionType === 'checkbox' || props.exportExcel) {
|
||||
// console.log('onSelect', record, selected, _selectedRows)
|
||||
if (props.selectionType === 'checkbox' || batchColumns.value.length > 0 || props.exportExcel) {
|
||||
if (selected) {
|
||||
_selectedRows.forEach(v => {
|
||||
if (v) {
|
||||
|
@ -300,20 +298,11 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
|
|||
})
|
||||
}
|
||||
else {
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
selectedRowKeys.value = selectedRowKeys.value.filter((v: any) => v !== record[props.rowKey])
|
||||
selectedRowKeys.value.splice(selectedRowKeys.value.indexOf(record[props.rowKey]), 1)
|
||||
delete selectedRecords.value[record[props.rowKey]]
|
||||
}
|
||||
|
||||
await nextTick(async () => {
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const filteredRows: any[] = []
|
||||
|
||||
selectedRowKeys.value.forEach(v => {
|
||||
filteredRows.push(selectedRecords.value[v])
|
||||
})
|
||||
selectedRows.value = filteredRows
|
||||
})
|
||||
await nextTick()
|
||||
selectedRows.value = [...selectedRowKeys.value.map(v => selectedRecords.value[v])]
|
||||
}
|
||||
else if (selected) {
|
||||
selectedRowKeys.value = record[props.rowKey]
|
||||
|
@ -327,7 +316,7 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
|
|||
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
async function onSelectAll(selected: boolean, _selectedRows: any[], changeRows: any[]) {
|
||||
// console.log(selected, selectedRows, changeRows)
|
||||
// console.log('onSelectAll', selected, selectedRows, changeRows)
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
changeRows.forEach((v: any) => {
|
||||
if (v) {
|
||||
|
@ -342,22 +331,19 @@ async function onSelectAll(selected: boolean, _selectedRows: any[], changeRows:
|
|||
})
|
||||
|
||||
if (!selected) {
|
||||
selectedRowKeys.value = selectedRowKeys.value.filter(v => {
|
||||
return selectedRecords.value[v]
|
||||
})
|
||||
selectedRowKeys.value.splice(0, selectedRowKeys.value.length, ...selectedRowKeys.value.filter(v => selectedRecords.value[v]))
|
||||
}
|
||||
|
||||
// console.log(selectedRowKeysBuffer.value, selectedRecords.value)
|
||||
|
||||
await nextTick(async () => {
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
const filteredRows: any[] = []
|
||||
await nextTick()
|
||||
selectedRows.value.splice(0, selectedRows.value.length, ...selectedRowKeys.value.map(v => selectedRecords.value[v]))
|
||||
}
|
||||
|
||||
selectedRowKeys.value.forEach(v => {
|
||||
filteredRows.push(selectedRecords.value[v])
|
||||
})
|
||||
selectedRows.value = filteredRows
|
||||
})
|
||||
function resetSelection() {
|
||||
selectedRowKeys.value = reactive([])
|
||||
selectedRows.value = reactive([])
|
||||
selectedRecords.value = reactive({})
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
|
@ -381,7 +367,7 @@ async function resetSearch() {
|
|||
updateFilter.value++
|
||||
}
|
||||
|
||||
watch(params, v => {
|
||||
const { stop: stopWatchParams, resume: resumeWatchParams } = watchPausable(params, v => {
|
||||
if (!init.value)
|
||||
return
|
||||
|
||||
|
@ -393,7 +379,6 @@ watch(params, v => {
|
|||
|
||||
watch(() => route.query, async () => {
|
||||
params.trash = route.query.trash === 'true'
|
||||
params.team_id = route.query.team_id
|
||||
|
||||
if (init.value)
|
||||
await get_list()
|
||||
|
@ -425,14 +410,16 @@ if (props.overwriteParams) {
|
|||
const rowSelection = computed(() => {
|
||||
if (batchColumns.value.length > 0 || props.selectionType || props.exportExcel) {
|
||||
return {
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onSelect,
|
||||
onSelectAll,
|
||||
getCheckboxProps: props?.getCheckboxProps,
|
||||
type: (batchColumns.value.length > 0 || props.exportExcel) ? 'checkbox' : props.selectionType,
|
||||
}
|
||||
}
|
||||
else { return null }
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}) as ComputedRef<TableProps['rowSelection']>
|
||||
|
||||
const hasSelectedRow = computed(() => {
|
||||
|
@ -440,18 +427,21 @@ const hasSelectedRow = computed(() => {
|
|||
})
|
||||
|
||||
function clickBatchEdit() {
|
||||
emit('clickBatchModify', batchColumns.value, selectedRowKeys.value)
|
||||
emit('clickBatchModify', batchColumns.value, selectedRowKeys.value, selectedRows.value)
|
||||
}
|
||||
|
||||
function initSortable() {
|
||||
useSortable(props, randomId, dataSource, rowsKeyIndexMap, expandKeysList)
|
||||
}
|
||||
|
||||
function changePage(page: number, page_size: number) {
|
||||
async function changePage(page: number, page_size: number) {
|
||||
stopWatchParams()
|
||||
Object.assign(params, {
|
||||
page,
|
||||
page_size,
|
||||
})
|
||||
resumeWatchParams()
|
||||
await get_list(page, page_size)
|
||||
}
|
||||
|
||||
const paginationSize = computed(() => {
|
||||
|
@ -529,10 +519,7 @@ const paginationSize = computed(() => {
|
|||
>
|
||||
{{ $gettext('Modify') }}
|
||||
</AButton>
|
||||
<ADivider
|
||||
v-if="!props.disableDelete"
|
||||
type="vertical"
|
||||
/>
|
||||
<ADivider type="vertical" />
|
||||
</template>
|
||||
|
||||
<slot
|
||||
|
@ -569,6 +556,7 @@ const paginationSize = computed(() => {
|
|||
{{ $gettext('Recover') }}
|
||||
</AButton>
|
||||
</APopconfirm>
|
||||
<ADivider type="vertical" />
|
||||
<APopconfirm
|
||||
v-if="params.trash"
|
||||
:cancel-text="$gettext('No')"
|
||||
|
@ -601,8 +589,14 @@ const paginationSize = computed(() => {
|
|||
.ant-table-scroll {
|
||||
.ant-table-body {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
.std-table {
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -624,6 +618,12 @@ const paginationSize = computed(() => {
|
|||
:deep(.ant-form-inline .ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ant-divider {
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import type { Column } from '@/components/StdDesign/types'
|
||||
|
||||
export function getPithyColumns(columns: Column[]) {
|
||||
return columns.filter(c => {
|
||||
return c.pithy === true && !c.hiddenInTable
|
||||
})
|
||||
}
|
|
@ -135,6 +135,7 @@ async function save() {
|
|||
content: configText.value,
|
||||
overwrite: true,
|
||||
site_category_id: data.value.site_category_id,
|
||||
sync_node_ids: data.value.sync_node_ids,
|
||||
}).then(r => {
|
||||
handle_response(r)
|
||||
router.push({
|
||||
|
|
|
@ -1,107 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import domain from '@/api/domain'
|
||||
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { Modal, notification } from 'ant-design-vue'
|
||||
|
||||
const node_map = ref({})
|
||||
const target = ref([])
|
||||
const overwrite = ref(false)
|
||||
const enabled = ref(false)
|
||||
const name = inject('name') as Ref<string>
|
||||
const [modal, ContextHolder] = Modal.useModal()
|
||||
function deploy() {
|
||||
modal.confirm({
|
||||
title: () => $ngettext('Do you want to deploy this file to remote server?', 'Do you want to deploy this file to remote servers?', target.value.length),
|
||||
mask: false,
|
||||
centered: true,
|
||||
okText: $gettext('OK'),
|
||||
cancelText: $gettext('Cancel'),
|
||||
onOk() {
|
||||
target.value.forEach(id => {
|
||||
const node_name = node_map.value[id]
|
||||
|
||||
// get source content
|
||||
domain.get(name.value).then(r => {
|
||||
domain.save(name.value, {
|
||||
name: name.value,
|
||||
content: r.config,
|
||||
overwrite: overwrite.value,
|
||||
|
||||
}, { headers: { 'X-Node-ID': id } }).then(async () => {
|
||||
notification.success({
|
||||
message: $gettext('Deploy successfully'),
|
||||
description:
|
||||
$gettext('Deploy %{conf_name} to %{node_name} successfully', { conf_name: name.value, node_name }),
|
||||
})
|
||||
if (enabled.value) {
|
||||
domain.enable(name.value, { headers: { 'X-Node-ID': id } }).then(() => {
|
||||
notification.success({
|
||||
message: $gettext('Enable successfully'),
|
||||
description:
|
||||
$gettext('Enable %{conf_name} in %{node_name} successfully', { conf_name: name.value, node_name }),
|
||||
})
|
||||
}).catch(e => {
|
||||
notification.error({
|
||||
message: $gettext('Enable %{conf_name} in %{node_name} failed', {
|
||||
conf_name: name.value,
|
||||
node_name,
|
||||
}),
|
||||
description: $gettext(e?.message ?? 'Server error'),
|
||||
})
|
||||
})
|
||||
}
|
||||
}).catch(e => {
|
||||
notification.error({
|
||||
message: $gettext('Deploy %{conf_name} to %{node_name} failed', {
|
||||
conf_name: name.value,
|
||||
node_name,
|
||||
}),
|
||||
description: $gettext(e?.message ?? 'Server error'),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ContextHolder />
|
||||
<NodeSelector
|
||||
v-model:target="target"
|
||||
v-model:map="node_map"
|
||||
class="mb-4"
|
||||
hidden-local
|
||||
/>
|
||||
<div class="node-deploy-control">
|
||||
<ACheckbox v-model:checked="enabled">
|
||||
{{ $gettext('Enable') }}
|
||||
</ACheckbox>
|
||||
<div class="overwrite">
|
||||
<ACheckbox v-model:checked="overwrite">
|
||||
{{ $gettext('Overwrite') }}
|
||||
</ACheckbox>
|
||||
<ATooltip placement="bottom">
|
||||
<template #title>
|
||||
{{ $gettext('Overwrite exist file') }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</ATooltip>
|
||||
</div>
|
||||
|
||||
<AButton
|
||||
:disabled="target.length === 0"
|
||||
type="primary"
|
||||
ghost
|
||||
@click="deploy"
|
||||
>
|
||||
{{ $gettext('Deploy') }}
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -6,11 +6,12 @@ import type { Ref } from 'vue'
|
|||
import domain from '@/api/domain'
|
||||
import site_category from '@/api/site_category'
|
||||
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
||||
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
|
||||
import StdSelector from '@/components/StdDesign/StdDataEntry/components/StdSelector.vue'
|
||||
import { formatDateTime } from '@/lib/helper'
|
||||
import { useSettingsStore } from '@/pinia'
|
||||
import Deploy from '@/views/site/components/Deploy.vue'
|
||||
import siteCategoryColumns from '@/views/site/site_category/columns'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
|
||||
const settings = useSettingsStore()
|
||||
|
@ -71,6 +72,7 @@ function on_change_enabled(checked: CheckedType) {
|
|||
<ACollapse
|
||||
v-model:active-key="active_key"
|
||||
ghost
|
||||
collapsible="header"
|
||||
>
|
||||
<ACollapsePanel
|
||||
key="1"
|
||||
|
@ -103,9 +105,33 @@ function on_change_enabled(checked: CheckedType) {
|
|||
<ACollapsePanel
|
||||
v-if="!settings.is_remote"
|
||||
key="2"
|
||||
:header="$gettext('Deploy')"
|
||||
>
|
||||
<Deploy />
|
||||
<template #header>
|
||||
{{ $gettext('Synchronization') }}
|
||||
</template>
|
||||
<template #extra>
|
||||
<APopover placement="bottomRight" :title="$gettext('Sync strategy')">
|
||||
<template #content>
|
||||
<div class="max-w-200px mb-2">
|
||||
{{ $gettext('When you enable/disable, delete, or save this site, '
|
||||
+ 'the nodes set in the site category and the nodes selected below will be synchronized.') }}
|
||||
</div>
|
||||
<div class="max-w-200px">
|
||||
{{ $gettext('Note, if the configuration file include other configurations or certificates, '
|
||||
+ 'please synchronize them to the remote nodes in advance.') }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="text-trueGray-600">
|
||||
<InfoCircleOutlined class="mr-1" />
|
||||
{{ $gettext('Sync strategy') }}
|
||||
</div>
|
||||
</APopover>
|
||||
</template>
|
||||
<NodeSelector
|
||||
v-model:target="data.sync_node_ids"
|
||||
class="mb-4"
|
||||
hidden-local
|
||||
/>
|
||||
</ACollapsePanel>
|
||||
<ACollapsePanel
|
||||
key="3"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script setup lang="tsx">
|
||||
import type { Site } from '@/api/domain'
|
||||
import type { SiteCategory } from '@/api/site_category'
|
||||
import type { Column } from '@/components/StdDesign/types'
|
||||
import domain from '@/api/domain'
|
||||
import site_category from '@/api/site_category'
|
||||
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
||||
|
@ -7,6 +9,7 @@ import InspectConfig from '@/views/config/InspectConfig.vue'
|
|||
import SiteDuplicate from '@/views/site/components/SiteDuplicate.vue'
|
||||
import columns from '@/views/site/site_list/columns'
|
||||
import { message } from 'ant-design-vue'
|
||||
import StdBatchEdit from '../../../components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
@ -77,6 +80,17 @@ function handle_click_duplicate(name: string) {
|
|||
show_duplicator.value = true
|
||||
target.value = name
|
||||
}
|
||||
|
||||
const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
|
||||
|
||||
async function handleClickBatchEdit(batchColumns: Column[], selectedRowKeys: string[], selectedRows: Site[]) {
|
||||
stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys, selectedRows)
|
||||
}
|
||||
|
||||
function handleBatchUpdated() {
|
||||
table.value?.get_list()
|
||||
table.value?.resetSelection()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -101,9 +115,9 @@ function handle_click_duplicate(name: string) {
|
|||
@click-edit="(r: string) => router.push({
|
||||
path: `/sites/${r}`,
|
||||
})"
|
||||
@click-batch-modify="handleClickBatchEdit"
|
||||
>
|
||||
<template #actions="{ record }">
|
||||
<ADivider type="vertical" />
|
||||
<AButton
|
||||
v-if="record.enabled"
|
||||
type="link"
|
||||
|
@ -146,6 +160,12 @@ function handle_click_duplicate(name: string) {
|
|||
</APopconfirm>
|
||||
</template>
|
||||
</StdTable>
|
||||
<StdBatchEdit
|
||||
ref="stdBatchEditRef"
|
||||
:api="domain"
|
||||
:columns
|
||||
@save="handleBatchUpdated"
|
||||
/>
|
||||
<SiteDuplicate
|
||||
v-model:visible="show_duplicator"
|
||||
:name="target"
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||
import site_category from '@/api/site_category'
|
||||
import {
|
||||
actualValueRender,
|
||||
type CustomRenderProps,
|
||||
datetime,
|
||||
} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||
import { input, select } from '@/components/StdDesign/StdDataEntry'
|
||||
import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
|
||||
import siteCategoryColumns from '@/views/site/site_category/columns'
|
||||
import { Badge } from 'ant-design-vue'
|
||||
|
||||
const columns: Column[] = [{
|
||||
|
@ -20,8 +22,18 @@ const columns: Column[] = [{
|
|||
title: () => $gettext('Category'),
|
||||
dataIndex: 'site_category_id',
|
||||
customRender: actualValueRender('site_category.name'),
|
||||
edit: {
|
||||
type: selector,
|
||||
selector: {
|
||||
api: site_category,
|
||||
columns: siteCategoryColumns,
|
||||
recordValueIndex: 'name',
|
||||
selectionType: 'radio',
|
||||
},
|
||||
},
|
||||
sorter: true,
|
||||
pithy: true,
|
||||
batch: true,
|
||||
}, {
|
||||
title: () => $gettext('Status'),
|
||||
dataIndex: 'enabled',
|
||||
|
|
|
@ -135,7 +135,6 @@ function handleAddStream() {
|
|||
})"
|
||||
>
|
||||
<template #actions="{ record }">
|
||||
<ADivider type="vertical" />
|
||||
<AButton
|
||||
v-if="record.enabled"
|
||||
type="link"
|
||||
|
|
|
@ -4,7 +4,8 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/uozi-tech/cosy/settings"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
cSettings "github.com/uozi-tech/cosy/settings"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gen"
|
||||
"gorm.io/gorm"
|
||||
|
@ -39,8 +40,8 @@ func main() {
|
|||
flag.StringVar(&confPath, "config", "app.ini", "Specify the configuration file")
|
||||
flag.Parse()
|
||||
|
||||
settings.Init(confPath)
|
||||
dbPath := path.Join(path.Dir(confPath), fmt.Sprintf("%s.db", settings.DataBaseSettings.Name))
|
||||
cSettings.Init(confPath)
|
||||
dbPath := path.Join(path.Dir(confPath), fmt.Sprintf("%s.db", settings.DatabaseSettings.Name))
|
||||
|
||||
var err error
|
||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||
|
|
|
@ -2,8 +2,9 @@ package model
|
|||
|
||||
type Site struct {
|
||||
Model
|
||||
Path string `json:"path"`
|
||||
Path string `json:"path" gorm:"uniqueIndex"`
|
||||
Advanced bool `json:"advanced"`
|
||||
SiteCategoryID uint64 `json:"site_category_id"`
|
||||
SiteCategory *SiteCategory `json:"site_category,omitempty"`
|
||||
SyncNodeIDs []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
|
|||
_site.Path = field.NewString(tableName, "path")
|
||||
_site.Advanced = field.NewBool(tableName, "advanced")
|
||||
_site.SiteCategoryID = field.NewUint64(tableName, "site_category_id")
|
||||
_site.SyncNodeIDs = field.NewField(tableName, "sync_node_ids")
|
||||
_site.SiteCategory = siteBelongsToSiteCategory{
|
||||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
|
@ -57,6 +58,7 @@ type site struct {
|
|||
Path field.String
|
||||
Advanced field.Bool
|
||||
SiteCategoryID field.Uint64
|
||||
SyncNodeIDs field.Field
|
||||
SiteCategory siteBelongsToSiteCategory
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
|
@ -81,6 +83,7 @@ func (s *site) updateTableName(table string) *site {
|
|||
s.Path = field.NewString(table, "path")
|
||||
s.Advanced = field.NewBool(table, "advanced")
|
||||
s.SiteCategoryID = field.NewUint64(table, "site_category_id")
|
||||
s.SyncNodeIDs = field.NewField(table, "sync_node_ids")
|
||||
|
||||
s.fillFieldMap()
|
||||
|
||||
|
@ -97,7 +100,7 @@ func (s *site) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||
}
|
||||
|
||||
func (s *site) fillFieldMap() {
|
||||
s.fieldMap = make(map[string]field.Expr, 8)
|
||||
s.fieldMap = make(map[string]field.Expr, 9)
|
||||
s.fieldMap["id"] = s.ID
|
||||
s.fieldMap["created_at"] = s.CreatedAt
|
||||
s.fieldMap["updated_at"] = s.UpdatedAt
|
||||
|
@ -105,6 +108,7 @@ func (s *site) fillFieldMap() {
|
|||
s.fieldMap["path"] = s.Path
|
||||
s.fieldMap["advanced"] = s.Advanced
|
||||
s.fieldMap["site_category_id"] = s.SiteCategoryID
|
||||
s.fieldMap["sync_node_ids"] = s.SyncNodeIDs
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue