mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 02:45:49 +02:00
feat: add category option for site
This commit is contained in:
parent
7ad5cac3b8
commit
207f80f858
16 changed files with 1452 additions and 508 deletions
|
@ -69,7 +69,7 @@ func GetSite(c *gin.Context) {
|
||||||
|
|
||||||
c.JSON(http.StatusOK, Site{
|
c.JSON(http.StatusOK, Site{
|
||||||
ModifiedAt: file.ModTime(),
|
ModifiedAt: file.ModTime(),
|
||||||
Advanced: site.Advanced,
|
Site: site,
|
||||||
Enabled: enabled,
|
Enabled: enabled,
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: string(origContent),
|
Config: string(origContent),
|
||||||
|
@ -102,7 +102,7 @@ func GetSite(c *gin.Context) {
|
||||||
|
|
||||||
c.JSON(http.StatusOK, Site{
|
c.JSON(http.StatusOK, Site{
|
||||||
ModifiedAt: file.ModTime(),
|
ModifiedAt: file.ModTime(),
|
||||||
Advanced: site.Advanced,
|
Site: site,
|
||||||
Enabled: enabled,
|
Enabled: enabled,
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: nginxConfig.FmtCode(),
|
Config: nginxConfig.FmtCode(),
|
||||||
|
@ -125,9 +125,10 @@ func SaveSite(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var json struct {
|
var json struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Content string `json:"content" binding:"required"`
|
Content string `json:"content" binding:"required"`
|
||||||
Overwrite bool `json:"overwrite"`
|
SiteCategoryID uint64 `json:"site_category_id"`
|
||||||
|
Overwrite bool `json:"overwrite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
|
@ -149,11 +150,18 @@ func SaveSite(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
|
enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
|
||||||
|
s := query.Site
|
||||||
|
|
||||||
|
_, err = s.Where(s.Path.Eq(path)).Update(s.SiteCategoryID, json.SiteCategoryID)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// rename the config file if needed
|
// rename the config file if needed
|
||||||
if name != json.Name {
|
if name != json.Name {
|
||||||
newPath := nginx.GetConfPath("sites-available", json.Name)
|
newPath := nginx.GetConfPath("sites-available", json.Name)
|
||||||
s := query.Site
|
_, _ = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
|
||||||
_, err = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
|
|
||||||
|
|
||||||
// check if dst file exists, do not rename
|
// check if dst file exists, do not rename
|
||||||
if helper.FileExists(newPath) {
|
if helper.FileExists(newPath) {
|
||||||
|
|
|
@ -3,15 +3,16 @@ package sites
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/cert"
|
"github.com/0xJacky/Nginx-UI/internal/cert"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/sashabaranov/go-openai"
|
"github.com/sashabaranov/go-openai"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Site struct {
|
type Site struct {
|
||||||
ModifiedAt time.Time `json:"modified_at"`
|
*model.Site
|
||||||
Advanced bool `json:"advanced"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ModifiedAt time.Time `json:"modified_at"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
AutoCert bool `json:"auto_cert"`
|
AutoCert bool `json:"auto_cert"`
|
||||||
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
|
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"sortablejs": "^1.15.3",
|
"sortablejs": "^1.15.3",
|
||||||
"universal-cookie": "^7.2.1",
|
"universal-cookie": "^7.2.1",
|
||||||
|
"unocss": "^0.63.6",
|
||||||
"vite-plugin-build-id": "0.4.2",
|
"vite-plugin-build-id": "0.4.2",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.12",
|
||||||
"vue-dompurify-html": "^5.1.0",
|
"vue-dompurify-html": "^5.1.0",
|
||||||
|
@ -51,6 +52,12 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^3.8.0",
|
"@antfu/eslint-config": "^3.8.0",
|
||||||
|
"@iconify-json/fa": "1.1.5",
|
||||||
|
"@iconify-json/tabler": "1.1.95",
|
||||||
|
"@iconify/tools": "3.0.5",
|
||||||
|
"@iconify/types": "^2.0.0",
|
||||||
|
"@iconify/utils": "^2.1.33",
|
||||||
|
"@iconify/vue": "4.1.1",
|
||||||
"@simplewebauthn/types": "^11.0.0",
|
"@simplewebauthn/types": "^11.0.0",
|
||||||
"@types/lodash": "^4.17.12",
|
"@types/lodash": "^4.17.12",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
|
|
1485
app/pnpm-lock.yaml
generated
1485
app/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { CertificateInfo } from '@/api/cert'
|
import type { CertificateInfo } from '@/api/cert'
|
||||||
import type { NgxConfig } from '@/api/ngx'
|
import type { NgxConfig } from '@/api/ngx'
|
||||||
import type { ChatComplicationMessage } from '@/api/openai'
|
import type { ChatComplicationMessage } from '@/api/openai'
|
||||||
|
import type { SiteCategory } from '@/api/site_category'
|
||||||
import type { PrivateKeyType } from '@/constants'
|
import type { PrivateKeyType } from '@/constants'
|
||||||
import Curd from '@/api/curd'
|
import Curd from '@/api/curd'
|
||||||
import http from '@/lib/http'
|
import http from '@/lib/http'
|
||||||
|
@ -16,6 +17,8 @@ export interface Site {
|
||||||
chatgpt_messages: ChatComplicationMessage[]
|
chatgpt_messages: ChatComplicationMessage[]
|
||||||
tokenized?: NgxConfig
|
tokenized?: NgxConfig
|
||||||
cert_info?: Record<number, CertificateInfo[]>
|
cert_info?: Record<number, CertificateInfo[]>
|
||||||
|
site_category_id: number
|
||||||
|
site_category?: SiteCategory
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoCertRequest {
|
export interface AutoCertRequest {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type Curd from '@/api/curd'
|
import type Curd from '@/api/curd'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
||||||
|
import { watchOnce } from '@vueuse/core'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
placeholder?: string
|
||||||
label?: string
|
label?: string
|
||||||
selectedKey: number | number[] | undefined | null
|
|
||||||
selectionType: 'radio' | 'checkbox'
|
selectionType: 'radio' | 'checkbox'
|
||||||
recordValueIndex: string // to index the value of the record
|
recordValueIndex: string // to index the value of the record
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
@ -24,9 +24,19 @@ const props = defineProps<{
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
valueApi?: Curd<any>
|
valueApi?: Curd<any>
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
getCheckboxProps?: (record: any) => any
|
||||||
|
hideInputContainer?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update:selectedKey'])
|
const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!selectedKey.value)
|
||||||
|
watchOnce(selectedKey, _init)
|
||||||
|
else
|
||||||
|
_init()
|
||||||
|
})
|
||||||
|
|
||||||
const getParams = computed(() => {
|
const getParams = computed(() => {
|
||||||
return props.getParams
|
return props.getParams
|
||||||
|
@ -34,19 +44,23 @@ const getParams = computed(() => {
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
const M_values = ref([]) as any
|
const M_values = ref([]) as Ref<any[]>
|
||||||
|
|
||||||
const init = _.debounce(_init, 500, {
|
const ComputedMValue = computed(() => {
|
||||||
leading: true,
|
return M_values.value.filter(v => v && Object.keys(v).length > 0)
|
||||||
trailing: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
init()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
const records = ref([]) as Ref<any[]>
|
const records = defineModel<any[]>('selectedRecords', {
|
||||||
|
default: () => [],
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
if (props.selectionType === 'radio')
|
||||||
|
M_values.value = [props.value]
|
||||||
|
else if (typeof selectedKey.value === 'object')
|
||||||
|
M_values.value = props.value || []
|
||||||
|
})
|
||||||
|
|
||||||
async function _init() {
|
async function _init() {
|
||||||
// valueApi is used to fetch items that are using itemKey as index value
|
// valueApi is used to fetch items that are using itemKey as index value
|
||||||
|
@ -55,22 +69,22 @@ async function _init() {
|
||||||
M_values.value = []
|
M_values.value = []
|
||||||
|
|
||||||
if (props.selectionType === 'radio') {
|
if (props.selectionType === 'radio') {
|
||||||
// M_values.value = [props.value] // not init value, we need to fetch them from api
|
// M_values.value = [props.value]
|
||||||
if (!props.value && props.selectedKey) {
|
// not init value, we need to fetch them from api
|
||||||
api.get(props.selectedKey, props.getParams).then(r => {
|
if (!props.value && selectedKey.value && selectedKey.value !== '0') {
|
||||||
|
api.get(selectedKey.value, props.getParams).then(r => {
|
||||||
M_values.value = [r]
|
M_values.value = [r]
|
||||||
records.value = [r]
|
records.value = [r]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (typeof props.selectedKey === 'object') {
|
else if (typeof selectedKey.value === 'object') {
|
||||||
M_values.value = props.value || []
|
// M_values.value = props.value || []
|
||||||
|
|
||||||
// not init value, we need to fetch them from api
|
// not init value, we need to fetch them from api
|
||||||
if (!props.value && (props.selectedKey?.length || 0) > 0) {
|
if (!props.value && (selectedKey.value?.length || 0) > 0) {
|
||||||
api.get_list({
|
api.get_list({
|
||||||
...props.getParams,
|
...props.getParams,
|
||||||
id: props.selectedKey,
|
id: selectedKey.value,
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
M_values.value = r.data
|
M_values.value = r.data
|
||||||
records.value = r.data
|
records.value = r.data
|
||||||
|
@ -85,11 +99,22 @@ function show() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedKeyBuffer = ref()
|
const selectedKeyBuffer = ref()
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
const selectedBuffer: Ref<any[]> = ref([])
|
||||||
|
|
||||||
if (props.selectionType === 'radio')
|
watch(selectedKey, () => {
|
||||||
selectedKeyBuffer.value = [props.selectedKey]
|
selectedKeyBuffer.value = _.clone(selectedKey.value)
|
||||||
else
|
})
|
||||||
selectedKeyBuffer.value = props.selectedKey
|
|
||||||
|
watch(records, v => {
|
||||||
|
selectedBuffer.value = [...v]
|
||||||
|
M_values.value = [...v]
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
selectedKeyBuffer.value = _.clone(selectedKey.value)
|
||||||
|
selectedBuffer.value = _.clone(records.value)
|
||||||
|
})
|
||||||
|
|
||||||
const computedSelectedKeys = computed({
|
const computedSelectedKeys = computed({
|
||||||
get() {
|
get() {
|
||||||
|
@ -103,76 +128,80 @@ const computedSelectedKeys = computed({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.selectedKey === undefined || props.selectedKey === null) {
|
|
||||||
if (props.selectionType === 'radio')
|
|
||||||
emit('update:selectedKey', '')
|
|
||||||
else
|
|
||||||
emit('update:selectedKey', [])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function ok() {
|
async function ok() {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
emit('update:selectedKey', selectedKeyBuffer.value)
|
selectedKey.value = selectedKeyBuffer.value
|
||||||
|
records.value = selectedBuffer.value
|
||||||
|
await nextTick()
|
||||||
M_values.value = _.clone(records.value)
|
M_values.value = _.clone(records.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
|
|
||||||
// function clear() {
|
// function clear() {
|
||||||
// M_values.value = []
|
// M_values.value = []
|
||||||
// emit('update:selectedKey', '')
|
// emit('update:selectedKey', '')
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
defineExpose({ show })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="std-selector-container">
|
<div>
|
||||||
<div
|
<div
|
||||||
class="std-selector"
|
v-if="!hideInputContainer"
|
||||||
@click="show"
|
class="std-selector-container"
|
||||||
>
|
>
|
||||||
<div class="chips-container">
|
<div
|
||||||
<ATag
|
class="std-selector"
|
||||||
v-for="(chipText, index) in M_values"
|
@click="show"
|
||||||
:key="index"
|
|
||||||
class="mr-1"
|
|
||||||
color="orange"
|
|
||||||
:bordered="false"
|
|
||||||
@click="show"
|
|
||||||
>
|
|
||||||
{{ chipText?.[recordValueIndex] }}
|
|
||||||
</ATag>
|
|
||||||
</div>
|
|
||||||
<AModal
|
|
||||||
:mask="false"
|
|
||||||
:open="visible"
|
|
||||||
:cancel-text="$gettext('Cancel')"
|
|
||||||
:ok-text="$gettext('Ok')"
|
|
||||||
:title="$gettext('Selector')"
|
|
||||||
:width="800"
|
|
||||||
destroy-on-close
|
|
||||||
@cancel="visible = false"
|
|
||||||
@ok="ok"
|
|
||||||
>
|
>
|
||||||
{{ description }}
|
<div class="chips-container">
|
||||||
<StdTable
|
<div v-if="props.recordValueIndex">
|
||||||
v-model:selected-row-keys="computedSelectedKeys"
|
<ATag
|
||||||
v-model:selected-rows="records"
|
v-for="(chipText, index) in ComputedMValue"
|
||||||
:api="api"
|
:key="index"
|
||||||
:columns="columns"
|
class="mr-1"
|
||||||
:disable-search="disableSearch"
|
color="orange"
|
||||||
pithy
|
:bordered="false"
|
||||||
:row-key="itemKey"
|
@click="show"
|
||||||
:get-params="getParams"
|
>
|
||||||
:selection-type="selectionType"
|
{{ chipText?.[recordValueIndex] }}
|
||||||
disable-query-params
|
</ATag>
|
||||||
/>
|
</div>
|
||||||
</AModal>
|
<div
|
||||||
|
v-else
|
||||||
|
class="text-gray-400"
|
||||||
|
>
|
||||||
|
{{ placeholder }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<AModal
|
||||||
|
:mask="false"
|
||||||
|
:open="visible"
|
||||||
|
:cancel-text="$gettext('Cancel')"
|
||||||
|
:ok-text="$gettext('Ok')"
|
||||||
|
:title="$gettext('Selector')"
|
||||||
|
:width="800"
|
||||||
|
destroy-on-close
|
||||||
|
@cancel="visible = false"
|
||||||
|
@ok="ok"
|
||||||
|
>
|
||||||
|
{{ description }}
|
||||||
|
<StdTable
|
||||||
|
v-model:selected-row-keys="computedSelectedKeys"
|
||||||
|
v-model:selected-rows="selectedBuffer"
|
||||||
|
:api
|
||||||
|
:columns
|
||||||
|
:disable-search
|
||||||
|
:row-key="itemKey"
|
||||||
|
:get-params
|
||||||
|
:selection-type
|
||||||
|
:get-checkbox-props
|
||||||
|
pithy
|
||||||
|
disable-query-params
|
||||||
|
/>
|
||||||
|
</AModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -180,7 +209,7 @@ watchEffect(() => {
|
||||||
.std-selector-container {
|
.std-selector-container {
|
||||||
min-height: 39.9px;
|
min-height: 39.9px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: self-start;
|
||||||
|
|
||||||
.std-selector {
|
.std-selector {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -195,7 +224,7 @@ watchEffect(() => {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
//margin: 0 10px 0 0;
|
//margin: 0 10px 0 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -203,9 +232,10 @@ watchEffect(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chips-container {
|
.dark {
|
||||||
span {
|
.std-selector {
|
||||||
margin: 2px;
|
border: 1px solid #424242;
|
||||||
|
background-color: #141414;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import VueDOMPurifyHTML from 'vue-dompurify-html'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import gettext from './gettext'
|
import gettext from './gettext'
|
||||||
import router from './routes'
|
import router from './routes'
|
||||||
import './style.css'
|
import 'virtual:uno.css'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
|
@ -40,7 +40,7 @@ const saving = ref(false)
|
||||||
const filename = ref('')
|
const filename = ref('')
|
||||||
const parse_error_status = ref(false)
|
const parse_error_status = ref(false)
|
||||||
const parse_error_message = ref('')
|
const parse_error_message = ref('')
|
||||||
const data = ref({})
|
const data = ref({}) as Ref<Site>
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
@ -134,6 +134,7 @@ async function save() {
|
||||||
name: filename.value || name.value,
|
name: filename.value || name.value,
|
||||||
content: configText.value,
|
content: configText.value,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
|
site_category_id: data.value.site_category_id,
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
handle_response(r)
|
handle_response(r)
|
||||||
router.push({
|
router.push({
|
||||||
|
|
|
@ -4,10 +4,13 @@ import type { ChatComplicationMessage } from '@/api/openai'
|
||||||
import type { CheckedType } from '@/types'
|
import type { CheckedType } from '@/types'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import domain from '@/api/domain'
|
import domain from '@/api/domain'
|
||||||
|
import site_category from '@/api/site_category'
|
||||||
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
||||||
|
import StdSelector from '@/components/StdDesign/StdDataEntry/components/StdSelector.vue'
|
||||||
import { formatDateTime } from '@/lib/helper'
|
import { formatDateTime } from '@/lib/helper'
|
||||||
import { useSettingsStore } from '@/pinia'
|
import { useSettingsStore } from '@/pinia'
|
||||||
import Deploy from '@/views/site/components/Deploy.vue'
|
import Deploy from '@/views/site/components/Deploy.vue'
|
||||||
|
import siteCategoryColumns from '@/views/site/site_category/columns'
|
||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
|
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
|
@ -73,18 +76,29 @@ function on_change_enabled(checked: CheckedType) {
|
||||||
key="1"
|
key="1"
|
||||||
:header="$gettext('Basic')"
|
:header="$gettext('Basic')"
|
||||||
>
|
>
|
||||||
<AFormItem :label="$gettext('Enabled')">
|
<AForm layout="vertical">
|
||||||
<ASwitch
|
<AFormItem :label="$gettext('Enabled')">
|
||||||
:checked="enabled"
|
<ASwitch
|
||||||
@change="on_change_enabled"
|
:checked="enabled"
|
||||||
/>
|
@change="on_change_enabled"
|
||||||
</AFormItem>
|
/>
|
||||||
<AFormItem :label="$gettext('Name')">
|
</AFormItem>
|
||||||
<AInput v-model:value="filename" />
|
<AFormItem :label="$gettext('Name')">
|
||||||
</AFormItem>
|
<AInput v-model:value="filename" />
|
||||||
<AFormItem :label="$gettext('Updated at')">
|
</AFormItem>
|
||||||
{{ formatDateTime(data.modified_at) }}
|
<AFormItem :label="$gettext('Category')">
|
||||||
</AFormItem>
|
<StdSelector
|
||||||
|
v-model:selected-key="data.site_category_id"
|
||||||
|
:api="site_category"
|
||||||
|
:columns="siteCategoryColumns"
|
||||||
|
record-value-index="name"
|
||||||
|
selection-type="radio"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
<AFormItem :label="$gettext('Updated at')">
|
||||||
|
{{ formatDateTime(data.modified_at) }}
|
||||||
|
</AFormItem>
|
||||||
|
</AForm>
|
||||||
</ACollapsePanel>
|
</ACollapsePanel>
|
||||||
<ACollapsePanel
|
<ACollapsePanel
|
||||||
v-if="!settings.is_remote"
|
v-if="!settings.is_remote"
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: [
|
|
||||||
'./index.html',
|
|
||||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
corePlugins: {
|
|
||||||
preflight: false,
|
|
||||||
},
|
|
||||||
}
|
|
66
app/uno.config.ts
Normal file
66
app/uno.config.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// uno.config.ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetIcons,
|
||||||
|
presetTypography,
|
||||||
|
presetUno,
|
||||||
|
presetWebFonts,
|
||||||
|
transformerDirectives,
|
||||||
|
transformerVariantGroup,
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
shortcuts: [],
|
||||||
|
rules: [],
|
||||||
|
variants: [
|
||||||
|
// 使用工具函数
|
||||||
|
matcher => {
|
||||||
|
if (!matcher.endsWith('!'))
|
||||||
|
return matcher
|
||||||
|
return {
|
||||||
|
matcher: matcher.slice(0, -1),
|
||||||
|
selector: s => `${s}!important`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
presets: [
|
||||||
|
presetUno(),
|
||||||
|
presetAttributify(),
|
||||||
|
presetIcons({
|
||||||
|
collections: {
|
||||||
|
tabler: () => import('@iconify-json/tabler/icons.json').then(i => i.default),
|
||||||
|
},
|
||||||
|
extraProperties: {
|
||||||
|
'display': 'inline-block',
|
||||||
|
'height': '1.2em',
|
||||||
|
'width': '1.2em',
|
||||||
|
'vertical-align': 'text-bottom',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
presetTypography(),
|
||||||
|
presetWebFonts(),
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives(),
|
||||||
|
transformerVariantGroup(),
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
pipeline: {
|
||||||
|
include: [
|
||||||
|
// default
|
||||||
|
/\.(vue|[jt]sx|ts)($|\?)/,
|
||||||
|
|
||||||
|
// 参考:https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
|
||||||
|
],
|
||||||
|
|
||||||
|
// exclude files
|
||||||
|
// exclude: []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,10 +1,10 @@
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
|
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
|
||||||
import DefineOptions from 'unplugin-vue-define-options/vite'
|
import DefineOptions from 'unplugin-vue-define-options/vite'
|
||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import vitePluginBuildId from 'vite-plugin-build-id'
|
import vitePluginBuildId from 'vite-plugin-build-id'
|
||||||
|
@ -34,9 +34,9 @@ export default defineConfig(({ mode }) => {
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
|
|
||||||
vitePluginBuildId(),
|
vitePluginBuildId(),
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
|
UnoCSS(),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [AntDesignVueResolver({ importStyle: false })],
|
resolvers: [AntDesignVueResolver({ importStyle: false })],
|
||||||
directoryAsNamespace: true,
|
directoryAsNamespace: true,
|
||||||
|
|
|
@ -2,6 +2,8 @@ package model
|
||||||
|
|
||||||
type Site struct {
|
type Site struct {
|
||||||
Model
|
Model
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Advanced bool `json:"advanced"`
|
Advanced bool `json:"advanced"`
|
||||||
|
SiteCategoryID uint64 `json:"site_category_id"`
|
||||||
|
SiteCategory *SiteCategory `json:"site_category,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,12 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
|
||||||
_site.DeletedAt = field.NewField(tableName, "deleted_at")
|
_site.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
_site.Path = field.NewString(tableName, "path")
|
_site.Path = field.NewString(tableName, "path")
|
||||||
_site.Advanced = field.NewBool(tableName, "advanced")
|
_site.Advanced = field.NewBool(tableName, "advanced")
|
||||||
|
_site.SiteCategoryID = field.NewUint64(tableName, "site_category_id")
|
||||||
|
_site.SiteCategory = siteBelongsToSiteCategory{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("SiteCategory", "model.SiteCategory"),
|
||||||
|
}
|
||||||
|
|
||||||
_site.fillFieldMap()
|
_site.fillFieldMap()
|
||||||
|
|
||||||
|
@ -43,13 +49,15 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
|
||||||
type site struct {
|
type site struct {
|
||||||
siteDo
|
siteDo
|
||||||
|
|
||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.Uint64
|
ID field.Uint64
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
DeletedAt field.Field
|
DeletedAt field.Field
|
||||||
Path field.String
|
Path field.String
|
||||||
Advanced field.Bool
|
Advanced field.Bool
|
||||||
|
SiteCategoryID field.Uint64
|
||||||
|
SiteCategory siteBelongsToSiteCategory
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
|
@ -72,6 +80,7 @@ func (s *site) updateTableName(table string) *site {
|
||||||
s.DeletedAt = field.NewField(table, "deleted_at")
|
s.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
s.Path = field.NewString(table, "path")
|
s.Path = field.NewString(table, "path")
|
||||||
s.Advanced = field.NewBool(table, "advanced")
|
s.Advanced = field.NewBool(table, "advanced")
|
||||||
|
s.SiteCategoryID = field.NewUint64(table, "site_category_id")
|
||||||
|
|
||||||
s.fillFieldMap()
|
s.fillFieldMap()
|
||||||
|
|
||||||
|
@ -88,13 +97,15 @@ func (s *site) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *site) fillFieldMap() {
|
func (s *site) fillFieldMap() {
|
||||||
s.fieldMap = make(map[string]field.Expr, 6)
|
s.fieldMap = make(map[string]field.Expr, 8)
|
||||||
s.fieldMap["id"] = s.ID
|
s.fieldMap["id"] = s.ID
|
||||||
s.fieldMap["created_at"] = s.CreatedAt
|
s.fieldMap["created_at"] = s.CreatedAt
|
||||||
s.fieldMap["updated_at"] = s.UpdatedAt
|
s.fieldMap["updated_at"] = s.UpdatedAt
|
||||||
s.fieldMap["deleted_at"] = s.DeletedAt
|
s.fieldMap["deleted_at"] = s.DeletedAt
|
||||||
s.fieldMap["path"] = s.Path
|
s.fieldMap["path"] = s.Path
|
||||||
s.fieldMap["advanced"] = s.Advanced
|
s.fieldMap["advanced"] = s.Advanced
|
||||||
|
s.fieldMap["site_category_id"] = s.SiteCategoryID
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s site) clone(db *gorm.DB) site {
|
func (s site) clone(db *gorm.DB) site {
|
||||||
|
@ -107,6 +118,77 @@ func (s site) replaceDB(db *gorm.DB) site {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type siteBelongsToSiteCategory struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategory) Where(conds ...field.Expr) *siteBelongsToSiteCategory {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategory) WithContext(ctx context.Context) *siteBelongsToSiteCategory {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategory) Session(session *gorm.Session) *siteBelongsToSiteCategory {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategory) Model(m *model.Site) *siteBelongsToSiteCategoryTx {
|
||||||
|
return &siteBelongsToSiteCategoryTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
type siteBelongsToSiteCategoryTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Find() (result *model.SiteCategory, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Append(values ...*model.SiteCategory) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Replace(values ...*model.SiteCategory) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Delete(values ...*model.SiteCategory) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a siteBelongsToSiteCategoryTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
type siteDo struct{ gen.DO }
|
type siteDo struct{ gen.DO }
|
||||||
|
|
||||||
// FirstByID Where("id=@id")
|
// FirstByID Where("id=@id")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue