Skip to content

Commit 5060054

Browse files
committed
feat: add filter of category for sites list
1 parent 2112c2f commit 5060054

File tree

10 files changed

+278
-80
lines changed

10 files changed

+278
-80
lines changed

api/sites/domain.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func GetSite(c *gin.Context) {
4949
}
5050

5151
s := query.Site
52-
site, err := s.Where(s.Path.Eq(path)).FirstOrInit()
52+
site, err := s.Where(s.Path.Eq(path)).FirstOrCreate()
5353
if err != nil {
5454
api.ErrHandler(c, err)
5555
return
@@ -300,6 +300,14 @@ func DeleteSite(c *gin.Context) {
300300
var err error
301301
name := c.Param("name")
302302
availablePath := nginx.GetConfPath("sites-available", name)
303+
304+
s := query.Site
305+
_, err = s.Where(s.Path.Eq(availablePath)).Unscoped().Delete(&model.Site{})
306+
if err != nil {
307+
api.ErrHandler(c, err)
308+
return
309+
}
310+
303311
enabledPath := nginx.GetConfPath("sites-enabled", name)
304312
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
305313
c.JSON(http.StatusNotFound, gin.H{

api/sites/list.go

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ import (
44
"github.com/0xJacky/Nginx-UI/api"
55
"github.com/0xJacky/Nginx-UI/internal/config"
66
"github.com/0xJacky/Nginx-UI/internal/nginx"
7+
"github.com/0xJacky/Nginx-UI/model"
8+
"github.com/0xJacky/Nginx-UI/query"
79
"github.com/gin-gonic/gin"
10+
"github.com/samber/lo"
11+
"github.com/spf13/cast"
812
"net/http"
913
"os"
14+
"path/filepath"
1015
"strings"
1116
)
1217

@@ -15,6 +20,7 @@ func GetSiteList(c *gin.Context) {
1520
enabled := c.Query("enabled")
1621
orderBy := c.Query("order_by")
1722
sort := c.DefaultQuery("sort", "desc")
23+
querySiteCategoryId := cast.ToUint64(c.Query("site_category_id"))
1824

1925
configFiles, err := os.ReadDir(nginx.GetConfPath("sites-available"))
2026
if err != nil {
@@ -28,6 +34,20 @@ func GetSiteList(c *gin.Context) {
2834
return
2935
}
3036

37+
s := query.Site
38+
sTx := s.Preload(s.SiteCategory)
39+
if querySiteCategoryId != 0 {
40+
sTx.Where(s.SiteCategoryID.Eq(querySiteCategoryId))
41+
}
42+
sites, err := sTx.Find()
43+
if err != nil {
44+
api.ErrHandler(c, err)
45+
return
46+
}
47+
sitesMap := lo.SliceToMap(sites, func(item *model.Site) (string, *model.Site) {
48+
return filepath.Base(item.Path), item
49+
})
50+
3151
enabledConfigMap := make(map[string]bool)
3252
for i := range enabledConfig {
3353
enabledConfigMap[enabledConfig[i].Name()] = true
@@ -38,28 +58,46 @@ func GetSiteList(c *gin.Context) {
3858
for i := range configFiles {
3959
file := configFiles[i]
4060
fileInfo, _ := file.Info()
41-
if !file.IsDir() {
42-
// name filter
43-
if name != "" && !strings.Contains(file.Name(), name) {
61+
if file.IsDir() {
62+
continue
63+
}
64+
// name filter
65+
if name != "" && !strings.Contains(file.Name(), name) {
66+
continue
67+
}
68+
// status filter
69+
if enabled != "" {
70+
if enabled == "true" && !enabledConfigMap[file.Name()] {
4471
continue
4572
}
46-
// status filter
47-
if enabled != "" {
48-
if enabled == "true" && !enabledConfigMap[file.Name()] {
49-
continue
50-
}
51-
if enabled == "false" && enabledConfigMap[file.Name()] {
52-
continue
53-
}
73+
if enabled == "false" && enabledConfigMap[file.Name()] {
74+
continue
5475
}
55-
configs = append(configs, config.Config{
56-
Name: file.Name(),
57-
ModifiedAt: fileInfo.ModTime(),
58-
Size: fileInfo.Size(),
59-
IsDir: fileInfo.IsDir(),
60-
Enabled: enabledConfigMap[file.Name()],
61-
})
6276
}
77+
var (
78+
siteCategoryId uint64
79+
siteCategory *model.SiteCategory
80+
)
81+
82+
if site, ok := sitesMap[file.Name()]; ok {
83+
siteCategoryId = site.SiteCategoryID
84+
siteCategory = site.SiteCategory
85+
}
86+
87+
// site category filter
88+
if querySiteCategoryId != 0 && siteCategoryId != querySiteCategoryId {
89+
continue
90+
}
91+
92+
configs = append(configs, config.Config{
93+
Name: file.Name(),
94+
ModifiedAt: fileInfo.ModTime(),
95+
Size: fileInfo.Size(),
96+
IsDir: fileInfo.IsDir(),
97+
Enabled: enabledConfigMap[file.Name()],
98+
SiteCategoryID: siteCategoryId,
99+
SiteCategory: siteCategory,
100+
})
63101
}
64102

65103
configs = config.Sort(orderBy, sort, configs)

app/.eslint-auto-import.mjs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
export default {
2+
globals: {
3+
$gettext: true,
4+
$ngettext: true,
5+
$npgettext: true,
6+
$pgettext: true,
7+
Component: true,
8+
ComponentPublicInstance: true,
9+
ComputedRef: true,
10+
DirectiveBinding: true,
11+
EffectScope: true,
12+
ExtractDefaultPropTypes: true,
13+
ExtractPropTypes: true,
14+
ExtractPublicPropTypes: true,
15+
InjectionKey: true,
16+
MaybeRef: true,
17+
MaybeRefOrGetter: true,
18+
PropType: true,
19+
Ref: true,
20+
VNode: true,
21+
WritableComputedRef: true,
22+
acceptHMRUpdate: true,
23+
computed: true,
24+
createApp: true,
25+
createPinia: true,
26+
customRef: true,
27+
defineAsyncComponent: true,
28+
defineComponent: true,
29+
defineStore: true,
30+
effectScope: true,
31+
getActivePinia: true,
32+
getCurrentInstance: true,
33+
getCurrentScope: true,
34+
h: true,
35+
inject: true,
36+
isProxy: true,
37+
isReactive: true,
38+
isReadonly: true,
39+
isRef: true,
40+
mapActions: true,
41+
mapGetters: true,
42+
mapState: true,
43+
mapStores: true,
44+
mapWritableState: true,
45+
markRaw: true,
46+
nextTick: true,
47+
onActivated: true,
48+
onBeforeMount: true,
49+
onBeforeRouteLeave: true,
50+
onBeforeRouteUpdate: true,
51+
onBeforeUnmount: true,
52+
onBeforeUpdate: true,
53+
onDeactivated: true,
54+
onErrorCaptured: true,
55+
onMounted: true,
56+
onRenderTracked: true,
57+
onRenderTriggered: true,
58+
onScopeDispose: true,
59+
onServerPrefetch: true,
60+
onUnmounted: true,
61+
onUpdated: true,
62+
onWatcherCleanup: true,
63+
provide: true,
64+
reactive: true,
65+
readonly: true,
66+
ref: true,
67+
resolveComponent: true,
68+
setActivePinia: true,
69+
setMapStoreSuffix: true,
70+
shallowReactive: true,
71+
shallowReadonly: true,
72+
shallowRef: true,
73+
storeToRefs: true,
74+
toRaw: true,
75+
toRef: true,
76+
toRefs: true,
77+
toValue: true,
78+
triggerRef: true,
79+
unref: true,
80+
useAttrs: true,
81+
useCssModule: true,
82+
useCssVars: true,
83+
useId: true,
84+
useLink: true,
85+
useModel: true,
86+
useRoute: true,
87+
useRouter: true,
88+
useSlots: true,
89+
useTemplateRef: true,
90+
watch: true,
91+
watchEffect: true,
92+
watchPostEffect: true,
93+
watchSyncEffect: true,
94+
},
95+
}

app/eslint.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import createConfig from '@antfu/eslint-config'
22
import sonarjs from 'eslint-plugin-sonarjs'
3+
import autoImport from './.eslint-auto-import.mjs'
34

45
export default createConfig(
56
{
67
stylistic: true,
78
ignores: ['**/version.json', 'tsconfig.json', 'tsconfig.node.json'],
9+
languageOptions: {
10+
globals: autoImport.globals,
11+
},
812
},
913
sonarjs.configs.recommended,
1014
{

app/src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ export function mask(maskObj: any): (args: CustomRenderProps) => JSX.Element {
4848
export function arrayToTextRender(args: CustomRenderProps) {
4949
return args.text?.join(', ')
5050
}
51-
export function actualValueRender(args: CustomRenderProps, actualDataIndex: string | string[]) {
52-
return get(args.record, actualDataIndex)
51+
export function actualValueRender(actualDataIndex: string | string[]) {
52+
return (args: CustomRenderProps) => {
53+
return get(args.record, actualDataIndex) || '/'
54+
}
5355
}
5456

5557
export function longTextWithEllipsis(len: number): (args: CustomRenderProps) => JSX.Element {

app/src/routes/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const routes: RouteRecordRaw[] = [
5252
children: [{
5353
path: 'list',
5454
name: 'Sites List',
55-
component: () => import('@/views/site/SiteList.vue'),
55+
component: () => import('@/views/site/site_list/SiteList.vue'),
5656
meta: {
5757
name: () => $gettext('Sites List'),
5858
},

app/src/views/site/SiteList.vue renamed to app/src/views/site/site_list/SiteList.vue

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,44 @@
11
<script setup lang="tsx">
2-
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
3-
import type { Column, JSXElements } from '@/components/StdDesign/types'
2+
import type { SiteCategory } from '@/api/site_category'
43
import domain from '@/api/domain'
4+
import site_category from '@/api/site_category'
55
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
6-
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
7-
import { input, select } from '@/components/StdDesign/StdDataEntry'
86
import InspectConfig from '@/views/config/InspectConfig.vue'
97
import SiteDuplicate from '@/views/site/components/SiteDuplicate.vue'
10-
import { Badge, message } from 'ant-design-vue'
11-
12-
const columns: Column[] = [{
13-
title: () => $gettext('Name'),
14-
dataIndex: 'name',
15-
sorter: true,
16-
pithy: true,
17-
edit: {
18-
type: input,
19-
},
20-
search: true,
21-
}, {
22-
title: () => $gettext('Status'),
23-
dataIndex: 'enabled',
24-
customRender: (args: CustomRenderProps) => {
25-
const template: JSXElements = []
26-
const { text } = args
27-
if (text === true || text > 0) {
28-
template.push(<Badge status="success" />)
29-
template.push($gettext('Enabled'))
30-
}
31-
else {
32-
template.push(<Badge status="warning" />)
33-
template.push($gettext('Disabled'))
34-
}
8+
import columns from '@/views/site/site_list/columns'
9+
import { message } from 'ant-design-vue'
3510
36-
return h('div', template)
37-
},
38-
search: {
39-
type: select,
40-
mask: {
41-
true: $gettext('Enabled'),
42-
false: $gettext('Disabled'),
43-
},
44-
},
45-
sorter: true,
46-
pithy: true,
47-
}, {
48-
title: () => $gettext('Updated at'),
49-
dataIndex: 'modified_at',
50-
customRender: datetime,
51-
sorter: true,
52-
pithy: true,
53-
}, {
54-
title: () => $gettext('Action'),
55-
dataIndex: 'action',
56-
}]
11+
const route = useRoute()
12+
const router = useRouter()
5713
5814
const table = ref()
59-
6015
const inspect_config = ref()
6116
17+
const siteCategoryId = ref(Number.parseInt(route.query.site_category_id as string) || 0)
18+
const siteCategories = ref([]) as Ref<SiteCategory[]>
19+
20+
watch(route, () => {
21+
inspect_config.value?.test()
22+
})
23+
24+
onMounted(async () => {
25+
while (true) {
26+
try {
27+
const { data, pagination } = await site_category.get_list()
28+
if (!data || !pagination)
29+
return
30+
siteCategories.value.push(...data)
31+
if (data.length < pagination?.per_page) {
32+
return
33+
}
34+
}
35+
catch (e: any) {
36+
message.error(e?.message ?? $gettext('Server error'))
37+
return
38+
}
39+
}
40+
})
41+
6242
function enable(name: string) {
6343
domain.enable(name).then(() => {
6444
message.success($gettext('Enabled successfully'))
@@ -97,26 +77,28 @@ function handle_click_duplicate(name: string) {
9777
show_duplicator.value = true
9878
target.value = name
9979
}
100-
101-
const route = useRoute()
102-
103-
watch(route, () => {
104-
inspect_config.value?.test()
105-
})
10680
</script>
10781

10882
<template>
10983
<ACard :title="$gettext('Manage Sites')">
11084
<InspectConfig ref="inspect_config" />
11185

86+
<ATabs v-model:active-key="siteCategoryId">
87+
<ATabPane :key="0" :tab="$gettext('All')" />
88+
<ATabPane v-for="c in siteCategories" :key="c.id" :tab="c.name" />
89+
</ATabs>
90+
11291
<StdTable
11392
ref="table"
11493
:api="domain"
11594
:columns="columns"
11695
row-key="name"
11796
disable-delete
11897
disable-view
119-
@click-edit="r => $router.push({
98+
:get-params="{
99+
site_category_id: siteCategoryId,
100+
}"
101+
@click-edit="(r: string) => router.push({
120102
path: `/sites/${r}`,
121103
})"
122104
>

0 commit comments

Comments
 (0)