show changes feature

This commit is contained in:
Alireza Ahmadi
2024-06-08 17:49:03 +02:00
parent 1b9d5e9378
commit 12fe21906e
12 changed files with 254 additions and 6 deletions
+6
View File
@@ -157,6 +157,12 @@ func (a *APIHandler) getHandler(c *gin.Context) {
level := c.Query("l") level := c.Query("l")
logs := a.ServerService.GetLogs(service, count, level) logs := a.ServerService.GetLogs(service, count, level)
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
case "changes":
actor := c.Query("a")
chngKey := c.Query("k")
count := c.Query("c")
changes := a.ConfigService.GetChanges(actor, chngKey, count)
jsonObj(c, changes, nil)
default: default:
jsonMsg(c, "API call", nil) jsonMsg(c, "API call", nil)
} }
+12
View File
@@ -28,6 +28,13 @@ func migrateDb() {
}() }()
fmt.Println("Start migrating database...") fmt.Println("Start migrating database...")
err = migrateClientSchema(tx) err = migrateClientSchema(tx)
if err != nil {
log.Fatal(err)
}
err = changesObj(tx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Migration done!") fmt.Println("Migration done!")
} }
@@ -81,5 +88,10 @@ func migrateClientSchema(db *gorm.DB) error {
} }
} }
} }
db.AutoMigrate(model.Client{})
return nil return nil
} }
func changesObj(db *gorm.DB) error {
return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error
}
+1 -1
View File
@@ -70,7 +70,7 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
Actor: "DepleteJob", Actor: "DepleteJob",
Key: "clients", Key: "clients",
Action: "disable", Action: "disable",
Obj: json.RawMessage(client.Name), Obj: json.RawMessage("\"" + client.Name + "\""),
}) })
} }
+19
View File
@@ -6,6 +6,7 @@ import (
"s-ui/config" "s-ui/config"
"s-ui/database" "s-ui/database"
"s-ui/database/model" "s-ui/database/model"
"s-ui/logger"
"s-ui/singbox" "s-ui/singbox"
"strconv" "strconv"
"time" "time"
@@ -342,3 +343,21 @@ func (s *ConfigService) contains(slice []string, item string) bool {
} }
return false return false
} }
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
c, _ := strconv.Atoi(count)
whereString := "`id`>0"
if len(actor) > 0 {
whereString += " and `actor`='" + actor + "'"
}
if len(chngKey) > 0 {
whereString += " and `key`='" + chngKey + "'"
}
db := database.GetDB()
var chngs []model.Changes
err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error
if err != nil {
logger.Warning(err)
}
return chngs
}
+140
View File
@@ -0,0 +1,140 @@
<template>
<v-dialog transition="dialog-bottom-transition" width="90%" max-width="800" :loading="loading">
<v-card class="rounded-lg">
<v-card-title>
<v-row>
<v-col>{{ $t('admin.changes') }}</v-col>
<v-spacer></v-spacer>
<v-col cols="auto">
<v-icon icon="mdi-close" @click="$emit('close')" />
</v-col>
</v-row>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-row>
<v-col cols="12" sm="4" md="3">
<v-select
hide-details
:label="$t('admin.actor')"
:items="['', 'DepleteJob', ...admins]"
v-model="user"
@update:model-value="loadData">
</v-select>
</v-col>
<v-col cols="12" sm="4" md="3">
<v-select
hide-details
:label="$t('admin.key')"
:items="['', 'inbounds', 'outbounds', 'clients', 'route', 'tls', 'experimental']"
v-model="key"
@update:model-value="loadData">
</v-select>
</v-col>
<v-col cols="6" sm="4" md="3">
<v-select
hide-details
:label="$t('count')"
:items="[10,20,30,50,100]"
v-model.number="chngCount"
@update:model-value="loadData">
</v-select>
</v-col>
<v-col cols="auto" align="center" justify="center">
<v-btn
icon="mdi-refresh"
variant="tonal"
:loading="loading"
@click="loadData">
<v-icon />
</v-btn>
</v-col>
</v-row>
<v-data-table
:headers="changesHeaders"
:items="changes"
item-value="id"
density="compact"
show-expand
items-per-page="10"
>
<template v-slot:item.dateTime="{ value }">
<v-chip variant="text" dir="ltr" density="compact">
{{ dateFormatted(value) }}
</v-chip>
</template>
<template v-slot:item.action="{ value }">
<v-chip density="compact">
{{ $t('actions.' + value) }}
</v-chip>
</template>
<template v-slot:expanded-row="{ columns, item }">
<tr>
<td :colspan="columns.length">
<v-card dir="ltr" v-if="item.index>0">Index: {{ item.index }}</v-card>
<v-card style="background-color: background" dir="ltr"><pre>{{ item.obj }}</pre></v-card>
</td>
</tr>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { i18n } from '@/locales'
import HttpUtils from '@/plugins/httputil'
export default {
props: ['admins', 'actor', 'visible'],
data() {
return {
loading: false,
changes: <any[]>[],
user: '',
key: '',
chngCount: 10,
expanded: [],
changesHeaders: [
{ title: 'ID', key: 'id' },
{ title: i18n.global.t('admin.date') + '-' + i18n.global.t('admin.time'), key: 'dateTime' },
{ title: i18n.global.t('admin.actor'), key: 'Actor' },
{ title: i18n.global.t('admin.key'), key: 'key' },
{ title: i18n.global.t('admin.action'), key: 'action' },
],
}
},
methods: {
async loadData() {
this.loading = true
const data = await HttpUtils.get('api/changes',{ a: this.user, k: this.key, c: this.chngCount })
if (data.success) {
this.changes = data.obj?? []
this.loading = false
}
},
dateFormatted(dt: number): string {
const date = new Date(dt*1000)
return date.toLocaleString(this.locale)
},
},
computed: {
locale() {
const l = i18n.global.locale.value
return l.replace('zh', 'zh-')
},
},
watch: {
visible(newValue) {
this.changes = []
this.user = this.$props.actor
this.key = ''
this.chngCount = 10
if (newValue) {
this.loadData()
}
},
},
}
</script>
+8
View File
@@ -23,6 +23,7 @@ export default {
version: "Version", version: "Version",
email: "Email", email: "Email",
commaSeparated: "(comma separated)", commaSeparated: "(comma separated)",
count: "Count",
error: { error: {
dplData: "Duplicate Data", dplData: "Duplicate Data",
}, },
@@ -85,11 +86,14 @@ export default {
actions: { actions: {
action: "Action", action: "Action",
add: "Add", add: "Add",
new: "Add",
edit: "Edit", edit: "Edit",
del: "Delete", del: "Delete",
save: "Save", save: "Save",
update: "Update", update: "Update",
submit: "Submit", submit: "Submit",
set: "Set",
disable: "Disable",
close: "Close", close: "Close",
restartApp: "Restart App", restartApp: "Restart App",
}, },
@@ -111,6 +115,10 @@ export default {
lastLogin: "Last login", lastLogin: "Last login",
date: "Date", date: "Date",
time: "Time", time: "Time",
changes: "Changes",
actor: "Actor",
key: "Key",
action: "Action",
}, },
setting: { setting: {
interface: "Interface", interface: "Interface",
+8
View File
@@ -23,6 +23,7 @@ export default {
version: "نسخه", version: "نسخه",
email: "ایمیل", email: "ایمیل",
commaSeparated: "(جداشده با کاما)", commaSeparated: "(جداشده با کاما)",
count: "تعداد",
error: { error: {
dplData: "داده تکراری", dplData: "داده تکراری",
}, },
@@ -84,11 +85,14 @@ export default {
actions: { actions: {
action: "فرمان", action: "فرمان",
add: "ایجاد", add: "ایجاد",
new: "ایجاد",
edit: "ویرایش", edit: "ویرایش",
del: "حذف", del: "حذف",
save: "ذخیره", save: "ذخیره",
update: "بروزرسانی", update: "بروزرسانی",
submit: "ارسال", submit: "ارسال",
set: "تنظیم",
disable: "غیرفعال",
close: "بستن", close: "بستن",
restartApp: "ریستارت پنل", restartApp: "ریستارت پنل",
}, },
@@ -110,6 +114,10 @@ export default {
lastLogin: "آخرین ورود", lastLogin: "آخرین ورود",
date: "تاریخ", date: "تاریخ",
time: "ساعت", time: "ساعت",
changes: "تغییرات",
actor: "مجری",
key: "کلید",
action: "عمل",
}, },
setting: { setting: {
interface: "نما", interface: "نما",
+8
View File
@@ -23,6 +23,7 @@ export default {
version: "Phiên bản", version: "Phiên bản",
email: "Email", email: "Email",
commaSeparated: "(được phân tách bằng dấu phẩy)", commaSeparated: "(được phân tách bằng dấu phẩy)",
count: "Đếm",
error: { error: {
dplData: "Dữ liệu trùng lặp", dplData: "Dữ liệu trùng lặp",
}, },
@@ -85,11 +86,14 @@ export default {
actions: { actions: {
action: "Hành động", action: "Hành động",
add: "Thêm", add: "Thêm",
new: "Thêm",
edit: "Chỉnh sửa", edit: "Chỉnh sửa",
del: "Xóa", del: "Xóa",
save: "Lưu", save: "Lưu",
update: "Cập nhật", update: "Cập nhật",
submit: "Gửi", submit: "Gửi",
set: "Đặt",
disable: "Vô hiệu hóa",
close: "Đóng", close: "Đóng",
restartApp: "Khởi động lại ứng dụng", restartApp: "Khởi động lại ứng dụng",
}, },
@@ -111,6 +115,10 @@ export default {
lastLogin: "Lân đăng nhập cuôi", lastLogin: "Lân đăng nhập cuôi",
date: "Ngày", date: "Ngày",
time: "Thời gian", time: "Thời gian",
changes: "Thay đổi",
actor: "Diễn viên",
key: "Khóa",
action: "Hành động",
}, },
setting: { setting: {
interface: "Giao diện", interface: "Giao diện",
+8
View File
@@ -23,6 +23,7 @@ export default {
version: "版本", version: "版本",
email: "电子邮件", email: "电子邮件",
commaSeparated: "(逗号分隔)", commaSeparated: "(逗号分隔)",
count: "计数",
error: { error: {
dplData: "重复数据", dplData: "重复数据",
}, },
@@ -85,11 +86,14 @@ export default {
actions: { actions: {
action: "操作", action: "操作",
add: "添加", add: "添加",
new: "添加",
edit: "编辑", edit: "编辑",
del: "删除", del: "删除",
save: "保存", save: "保存",
update: "更新", update: "更新",
submit: "提交", submit: "提交",
set: "设置",
disable: "禁用",
close: "关闭", close: "关闭",
restartApp: "重启面板", restartApp: "重启面板",
}, },
@@ -111,6 +115,10 @@ export default {
lastLogin: "上次登录", lastLogin: "上次登录",
date: "日期", date: "日期",
time: "时间", time: "时间",
changes: "更改",
actor: "执行者",
key: "键",
action: "操作",
}, },
setting: { setting: {
interface: "界面", interface: "界面",
+8
View File
@@ -24,6 +24,7 @@ export default {
version: "版本", version: "版本",
email: "電子郵件", email: "電子郵件",
commaSeparated: "(逗號分隔)", commaSeparated: "(逗號分隔)",
count: "計數",
error: { error: {
dplData: "重複數據", dplData: "重複數據",
}, },
@@ -86,11 +87,14 @@ export default {
actions: { actions: {
action: "操作", action: "操作",
add: "添加", add: "添加",
new: "添加",
edit: "編輯", edit: "編輯",
del: "刪除", del: "刪除",
save: "保存", save: "保存",
update: "更新", update: "更新",
submit: "提交", submit: "提交",
set: "設置",
disable: "禁用",
close: "關閉", close: "關閉",
restartApp: "重啟面板", restartApp: "重啟面板",
}, },
@@ -112,6 +116,10 @@ export default {
lastLogin: "上次登入", lastLogin: "上次登入",
date: "日期", date: "日期",
time: "時間", time: "時間",
changes: "更改",
actor: "執行者",
key: "鍵",
action: "操作",
}, },
setting: { setting: {
interface: "界面", interface: "界面",
+2 -2
View File
@@ -9,7 +9,7 @@ import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles' import 'vuetify/styles'
import colors from 'vuetify/util/colors' import colors from 'vuetify/util/colors'
import { fa, en } from 'vuetify/locale' import { fa, en, vi, zhHans as zhcn, zhHant as zhtw } from 'vuetify/locale'
// Composables // Composables
import { createVuetify } from 'vuetify' import { createVuetify } from 'vuetify'
@@ -53,6 +53,6 @@ export default createVuetify({
locale: { locale: {
locale: localStorage.getItem("locale") ?? 'en', locale: localStorage.getItem("locale") ?? 'en',
fallback: 'en', fallback: 'en',
messages: { en, fa }, messages: { en, fa, vi, zhcn, zhtw },
}, },
}) })
+34 -3
View File
@@ -6,6 +6,18 @@
@close="closeEditModal" @close="closeEditModal"
@save="saveEditModal" @save="saveEditModal"
/> />
<ChngModal
v-model="changesModal.visible"
:visible="changesModal.visible"
:admins="users.map((u:any) => u.username)"
:actor="changesModal.actor"
@close="closeChangesModal"
/>
<v-row>
<v-col cols="12" justify="center" align="center">
<v-btn color="primary" @click="showChangesModal('')">{{ $t('admin.changes') }}</v-btn>
</v-col>
</v-row>
<v-row> <v-row>
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>users" :key="item.id"> <v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>users" :key="item.id">
<v-card rounded="xl" elevation="5" min-width="200" :title="item.username"> <v-card rounded="xl" elevation="5" min-width="200" :title="item.username">
@@ -38,6 +50,10 @@
<v-icon /> <v-icon />
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip> <v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
</v-btn> </v-btn>
<v-btn icon="mdi-list-box-outline" @click="showChangesModal(item.username)">
<v-icon />
<v-tooltip activator="parent" location="top" :text="$t('admin.changes')"></v-tooltip>
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-col> </v-col>
@@ -45,9 +61,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import AdminModal from '@/layouts/modals/Admin.vue'; import AdminModal from '@/layouts/modals/Admin.vue'
import HttpUtils from '@/plugins/httputil'; import ChngModal from '@/layouts/modals/Changes.vue'
import { Ref, ref, inject, onMounted } from 'vue'; import HttpUtils from '@/plugins/httputil'
import { Ref, ref, inject, onMounted } from 'vue'
const loading:Ref = inject('loading')?? ref(false) const loading:Ref = inject('loading')?? ref(false)
@@ -75,6 +92,7 @@ const showEditModal = (user: any) => {
} }
const closeEditModal = () => { const closeEditModal = () => {
editModal.value.visible = false editModal.value.visible = false
editModal.value.user = {}
} }
const saveEditModal = async (data:any) => { const saveEditModal = async (data:any) => {
loading.value=true loading.value=true
@@ -88,4 +106,17 @@ const saveEditModal = async (data:any) => {
loading.value=false loading.value=false
} }
} }
const changesModal = ref({
visible: false,
actor: '',
})
const showChangesModal = (actor: string) => {
changesModal.value.actor = actor
changesModal.value.visible = true
}
const closeChangesModal = () => {
changesModal.value.visible = false
changesModal.value.actor = ''
}
</script> </script>