Skip to content

Commit 994b908

Browse files
committed
feat: add notifications for renew certs #192
1 parent ddb6cee commit 994b908

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1971
-594
lines changed

.idea/dataSources.xml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/cosy/cosy.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type Ctx[T any] struct {
2727
preloads []string
2828
scan func(tx *gorm.DB) any
2929
transformer func(*T) any
30+
permanentlyDelete bool
3031
SelectedFields []string
3132
}
3233

@@ -128,6 +129,11 @@ func (c *Ctx[T]) Abort() {
128129
c.abort = true
129130
}
130131

132+
func (c *Ctx[T]) PermanentlyDelete() *Ctx[T] {
133+
c.permanentlyDelete = true
134+
return c
135+
}
136+
131137
func (c *Ctx[T]) GormScope(hook func(tx *gorm.DB) *gorm.DB) *Ctx[T] {
132138
c.gormScopes = append(c.gormScopes, hook)
133139
return c

api/cosy/delete.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ func (c *Ctx[T]) Destroy() {
2929
return
3030
}
3131

32+
if c.permanentlyDelete {
33+
result = result.Unscoped()
34+
}
35+
3236
err = result.Delete(&dbModel).Error
3337
if err != nil {
3438
errHandler(c.ctx, err)

api/notification/notification.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package notification
2+
3+
import (
4+
"github.com/0xJacky/Nginx-UI/api"
5+
"github.com/0xJacky/Nginx-UI/api/cosy"
6+
"github.com/0xJacky/Nginx-UI/model"
7+
"github.com/0xJacky/Nginx-UI/query"
8+
"github.com/gin-gonic/gin"
9+
"github.com/spf13/cast"
10+
"net/http"
11+
)
12+
13+
func Get(c *gin.Context) {
14+
n := query.Notification
15+
16+
id := cast.ToInt(c.Param("id"))
17+
18+
data, err := n.FirstByID(id)
19+
20+
if err != nil {
21+
api.ErrHandler(c, err)
22+
return
23+
}
24+
25+
c.JSON(http.StatusOK, data)
26+
}
27+
28+
func GetList(c *gin.Context) {
29+
cosy.Core[model.Notification](c).PagingList()
30+
}
31+
32+
func Destroy(c *gin.Context) {
33+
cosy.Core[model.Notification](c).
34+
PermanentlyDelete().
35+
Destroy()
36+
}
37+
38+
func DestroyAll(c *gin.Context) {
39+
db := model.UseDB()
40+
// remove all records
41+
err := db.Exec("DELETE FROM notifications").Error
42+
43+
if err != nil {
44+
api.ErrHandler(c, err)
45+
return
46+
}
47+
// reset auto increment
48+
err = db.Exec("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'notifications';").Error
49+
50+
if err != nil {
51+
api.ErrHandler(c, err)
52+
return
53+
}
54+
55+
c.JSON(http.StatusOK, gin.H{
56+
"message": "ok",
57+
})
58+
}

api/notification/router.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package notification
2+
3+
import "github.com/gin-gonic/gin"
4+
5+
func InitRouter(r *gin.RouterGroup) {
6+
r.GET("notifications", GetList)
7+
r.GET("notification/:id", Get)
8+
r.DELETE("notification/:id", Destroy)
9+
r.DELETE("notifications", DestroyAll)
10+
}

app/components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ declare module 'vue' {
4040
AListItem: typeof import('ant-design-vue/es')['ListItem']
4141
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
4242
AMenu: typeof import('ant-design-vue/es')['Menu']
43+
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
4344
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
4445
AModal: typeof import('ant-design-vue/es')['Modal']
4546
APagination: typeof import('ant-design-vue/es')['Pagination']
@@ -73,6 +74,8 @@ declare module 'vue' {
7374
LogoLogo: typeof import('./src/components/Logo/Logo.vue')['default']
7475
NginxControlNginxControl: typeof import('./src/components/NginxControl/NginxControl.vue')['default']
7576
NodeSelectorNodeSelector: typeof import('./src/components/NodeSelector/NodeSelector.vue')['default']
77+
NotificationClearNotifications: typeof import('./src/components/Notification/ClearNotifications.vue')['default']
78+
NotificationNotification: typeof import('./src/components/Notification/Notification.vue')['default']
7679
PageHeaderPageHeader: typeof import('./src/components/PageHeader/PageHeader.vue')['default']
7780
RouterLink: typeof import('vue-router')['RouterLink']
7881
RouterView: typeof import('vue-router')['RouterView']

app/gettext.config.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
const i18n = require('./i18n.json')
33

44
module.exports = {
5+
input: {
6+
include: ["**/*.js", "**/*.ts", "**/*.vue", "**/*.jsx", "**/*.tsx"]
7+
},
58
output: {
69
locales: Object.keys(i18n),
710
},

app/src/api/notification.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ModelBase } from '@/api/curd'
2+
import Curd from '@/api/curd'
3+
import http from '@/lib/http'
4+
5+
export interface Notification extends ModelBase {
6+
type: string
7+
title: string
8+
details: string
9+
}
10+
11+
class NotificationCurd extends Curd<Notification> {
12+
public clear() {
13+
return http.delete(this.plural)
14+
}
15+
}
16+
17+
const notification = new NotificationCurd('/notification')
18+
19+
export default notification
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<script setup lang="ts">
2+
import { BellOutlined, CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, InfoCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
3+
import { useGettext } from 'vue3-gettext'
4+
import type { Ref } from 'vue'
5+
import { message } from 'ant-design-vue'
6+
import notification from '@/api/notification'
7+
import type { Notification } from '@/api/notification'
8+
import { NotificationTypeT } from '@/constants'
9+
import { useUserStore } from '@/pinia'
10+
11+
const { $gettext } = useGettext()
12+
const loading = ref(false)
13+
14+
const { unreadCount } = storeToRefs(useUserStore())
15+
16+
const data = ref([]) as Ref<Notification[]>
17+
function init() {
18+
loading.value = true
19+
notification.get_list().then(r => {
20+
data.value = r.data
21+
unreadCount.value = r.pagination.total
22+
}).catch(e => {
23+
message.error($gettext(e?.message ?? 'Server error'))
24+
}).finally(() => {
25+
loading.value = false
26+
})
27+
}
28+
29+
onMounted(() => {
30+
init()
31+
})
32+
33+
const open = ref(false)
34+
35+
watch(open, v => {
36+
if (v)
37+
init()
38+
})
39+
40+
function clear() {
41+
notification.clear().then(() => {
42+
message.success($gettext('Cleared successfully'))
43+
data.value = []
44+
unreadCount.value = 0
45+
}).catch(e => {
46+
message.error($gettext(e?.message ?? 'Server error'))
47+
})
48+
}
49+
50+
function remove(id: number) {
51+
notification.destroy(id).then(() => {
52+
message.success($gettext('Removed successfully'))
53+
init()
54+
}).catch(e => {
55+
message.error($gettext(e?.message ?? 'Server error'))
56+
})
57+
}
58+
59+
const router = useRouter()
60+
function viewAll() {
61+
router.push('/notifications')
62+
open.value = false
63+
}
64+
</script>
65+
66+
<template>
67+
<span class="cursor-pointer">
68+
<APopover
69+
v-model:open="open"
70+
placement="bottomRight"
71+
overlay-class-name="notification-popover"
72+
trigger="click"
73+
>
74+
<ABadge
75+
:count="unreadCount"
76+
dot
77+
>
78+
<BellOutlined />
79+
</ABadge>
80+
<template #content>
81+
<div class="flex justify-between items-center p-2">
82+
<h3 class="mb-0">{{ $gettext('Notifications') }}</h3>
83+
<APopconfirm
84+
:cancel-text="$gettext('No')"
85+
:ok-text="$gettext('OK')"
86+
:title="$gettext('Are you sure you want to clear all notifications?')"
87+
placement="bottomRight"
88+
@confirm="clear"
89+
>
90+
<a>
91+
{{ $gettext('Clear') }}
92+
</a>
93+
</APopconfirm>
94+
</div>
95+
96+
<ADivider class="mt-2 mb-2" />
97+
98+
<AList
99+
:data-source="data"
100+
class="max-h-96 overflow-scroll"
101+
>
102+
<template #renderItem="{ item }">
103+
<AListItem>
104+
<template #actions>
105+
<span
106+
key="list-loadmore-remove"
107+
class="cursor-pointer"
108+
@click="remove(item.id)"
109+
>
110+
<DeleteOutlined />
111+
</span>
112+
</template>
113+
<AListItemMeta
114+
:title="item.title"
115+
:description="item.details"
116+
>
117+
<template #avatar>
118+
<div>
119+
<CloseCircleOutlined
120+
v-if="item.type === NotificationTypeT.Error"
121+
class="text-red-500"
122+
/>
123+
<WarningOutlined
124+
v-else-if="item.type === NotificationTypeT.Warning"
125+
class="text-orange-400"
126+
/>
127+
<InfoCircleOutlined
128+
v-else-if="item.type === NotificationTypeT.Info"
129+
class="text-blue-500"
130+
/>
131+
<CheckCircleOutlined
132+
v-else-if="item.type === NotificationTypeT.Success"
133+
class="text-green-500"
134+
/>
135+
</div>
136+
</template>
137+
</AListItemMeta>
138+
</AListItem>
139+
</template>
140+
</AList>
141+
<ADivider class="m-0 mb-2" />
142+
<div class="flex justify-center p-2">
143+
<a @click="viewAll">{{ $gettext('View all notifications') }}</a>
144+
</div>
145+
</template>
146+
</APopover>
147+
</span>
148+
</template>
149+
150+
<style lang="less">
151+
.notification-popover {
152+
width: 400px;
153+
}
154+
</style>
155+
156+
<style scoped lang="less">
157+
:deep(.ant-list-item-meta) {
158+
align-items: center !important;
159+
}
160+
161+
:deep(.ant-list-item-meta-avatar) {
162+
font-size: 24px;
163+
}
164+
</style>

app/src/components/StdDesign/StdDataDisplay/StdCurd.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,15 @@ const selectedRowKeys = ref([])
107107
<template>
108108
<div class="std-curd">
109109
<ACard :title="title || $gettext('Table')">
110-
<template
111-
v-if="!disableAdd"
112-
#extra
113-
>
114-
<a @click="add">{{ $gettext('Add') }}</a>
110+
<template #extra>
111+
<ASpace>
112+
<a
113+
v-if="!disableAdd"
114+
@click="add"
115+
>{{ $gettext('Add') }}</a>
116+
117+
<slot name="extra" />
118+
</ASpace>
115119
</template>
116120

117121
<StdTable

0 commit comments

Comments
 (0)