diff --git a/.db b/.db deleted file mode 100644 index e69de29b..00000000 diff --git a/api/sites/router.go b/api/sites/router.go index ba33248a..093ca51a 100644 --- a/api/sites/router.go +++ b/api/sites/router.go @@ -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) diff --git a/api/sites/domain.go b/api/sites/site.go similarity index 85% rename from api/sites/domain.go rename to api/sites/site.go index 24466233..66d32e03 100644 --- a/api/sites/domain.go +++ b/api/sites/site.go @@ -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" ) @@ -125,10 +127,11 @@ func SaveSite(c *gin.Context) { } var json struct { - Name string `json:"name" binding:"required"` - Content string `json:"content" binding:"required"` - SiteCategoryID uint64 `json:"site_category_id"` - Overwrite bool `json:"overwrite"` + 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"` } if !api.BindAndValid(c, &json) { @@ -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() +} diff --git a/api/sites/sites.go b/api/sites/type.go similarity index 100% rename from api/sites/sites.go rename to api/sites/type.go diff --git a/app/.eslint-auto-import.mjs b/app/.eslint-auto-import.mjs index 4046796e..d09af314 100644 --- a/app/.eslint-auto-import.mjs +++ b/app/.eslint-auto-import.mjs @@ -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 + } } diff --git a/app/components.d.ts b/app/components.d.ts index 42e75225..89bb9ba5 100644 --- a/app/components.d.ts +++ b/app/components.d.ts @@ -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'] diff --git a/app/eslint.config.mjs b/app/eslint.config.mjs index 7629c78a..c6ffbe03 100644 --- a/app/eslint.config.mjs +++ b/app/eslint.config.mjs @@ -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, }, diff --git a/app/src/api/domain.ts b/app/src/api/domain.ts index 4190cc11..de2027a5 100644 --- a/app/src/api/domain.ts +++ b/app/src/api/domain.ts @@ -19,6 +19,7 @@ export interface Site { cert_info?: Record site_category_id: number site_category?: SiteCategory + sync_node_ids: number[] } export interface AutoCertRequest { diff --git a/app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue b/app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue index b04c87aa..177ac3d2 100644 --- a/app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue +++ b/app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue @@ -1,48 +1,60 @@ @@ -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" > +

{{ $gettext('Belows are selected items that you want to batch modify') }}

+ + +

{{ $gettext('Leave blank if do not want to modify') }}

- + diff --git a/app/src/components/StdDesign/StdDataDisplay/StdCurd.vue b/app/src/components/StdDesign/StdDataDisplay/StdCurd.vue index 62016a53..38892a3e 100644 --- a/app/src/components/StdDesign/StdDataDisplay/StdCurd.vue +++ b/app/src/components/StdDesign/StdDataDisplay/StdCurd.vue @@ -1,8 +1,8 @@