进一步补充后端API
This commit is contained in:
@@ -28,7 +28,8 @@
|
|||||||
currentTicket: null,
|
currentTicket: null,
|
||||||
dashboard: null,
|
dashboard: null,
|
||||||
busy: false,
|
busy: false,
|
||||||
modal: null // { type, data }
|
modal: null, // { type, data }
|
||||||
|
configTab: "site"
|
||||||
};
|
};
|
||||||
|
|
||||||
boot();
|
boot();
|
||||||
@@ -112,6 +113,8 @@
|
|||||||
} else if (state.route === "dashboard-node") {
|
} else if (state.route === "dashboard-node") {
|
||||||
state.dashboard = unwrap(await request(api.dashboardSummary));
|
state.dashboard = unwrap(await request(api.dashboardSummary));
|
||||||
state.nodes = toArray(unwrap(await request(api.serverNodes)));
|
state.nodes = toArray(unwrap(await request(api.serverNodes)));
|
||||||
|
} else if (state.route === "system-config") {
|
||||||
|
state.config = unwrap(await request(api.adminConfig));
|
||||||
} else if (state.route === "overview") {
|
} else if (state.route === "overview") {
|
||||||
state.dashboard = unwrap(await request(api.dashboardSummary));
|
state.dashboard = unwrap(await request(api.dashboardSummary));
|
||||||
}
|
}
|
||||||
@@ -133,6 +136,7 @@
|
|||||||
if (action === "nav") { window.location.hash = actionEl.getAttribute("data-route") || "overview"; return; }
|
if (action === "nav") { window.location.hash = actionEl.getAttribute("data-route") || "overview"; return; }
|
||||||
if (action === "refresh") { refreshAll(); return; }
|
if (action === "refresh") { refreshAll(); return; }
|
||||||
if (action === "modal-close") { state.modal = null; render(); return; }
|
if (action === "modal-close") { state.modal = null; render(); return; }
|
||||||
|
if (action === "config-tab") { state.configTab = actionEl.getAttribute("data-tab"); render(); return; }
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
if (action === "plan-add") { state.modal = { type: "plan", data: {} }; render(); return; }
|
if (action === "plan-add") { state.modal = { type: "plan", data: {} }; render(); return; }
|
||||||
@@ -179,6 +183,43 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === "node-add") { state.modal = { type: "node", data: {} }; render(); return; }
|
||||||
|
if (action === "node-edit") {
|
||||||
|
var node = state.nodes.find(n => n.id == actionEl.getAttribute("data-id"));
|
||||||
|
state.modal = { type: "node", data: node }; render(); return;
|
||||||
|
}
|
||||||
|
if (action === "node-copy") {
|
||||||
|
adminPost(api.adminBase + "/server/manage/copy", { id: parseInt(actionEl.getAttribute("data-id")) }).then(hydrateRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "node-delete") {
|
||||||
|
if (!confirm("Are you sure?")) return;
|
||||||
|
adminPost(api.adminBase + "/server/manage/drop", { id: parseInt(actionEl.getAttribute("data-id")) }).then(hydrateRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "group-add") { state.modal = { type: "group", data: {} }; render(); return; }
|
||||||
|
if (action === "group-edit") {
|
||||||
|
var group = state.groups.find(g => g.id == actionEl.getAttribute("data-id"));
|
||||||
|
state.modal = { type: "group", data: group }; render(); return;
|
||||||
|
}
|
||||||
|
if (action === "group-delete") {
|
||||||
|
if (!confirm("Are you sure?")) return;
|
||||||
|
adminPost(api.adminBase + "/server/group/drop", { id: parseInt(actionEl.getAttribute("data-id")) }).then(hydrateRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "route-add") { state.modal = { type: "route", data: {} }; render(); return; }
|
||||||
|
if (action === "route-edit") {
|
||||||
|
var route = state.routes.find(r => r.id == actionEl.getAttribute("data-id"));
|
||||||
|
state.modal = { type: "route", data: route }; render(); return;
|
||||||
|
}
|
||||||
|
if (action === "route-delete") {
|
||||||
|
if (!confirm("Are you sure?")) return;
|
||||||
|
adminPost(api.adminBase + "/server/route/drop", { id: parseInt(actionEl.getAttribute("data-id")) }).then(hydrateRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Previous handlers
|
// Previous handlers
|
||||||
if (action === "approve-all") { adminPost(api.realnameBase + "/approve-all", {}).then(hydrateRoute); return; }
|
if (action === "approve-all") { adminPost(api.realnameBase + "/approve-all", {}).then(hydrateRoute); return; }
|
||||||
if (action === "sync-all") { adminPost(api.realnameBase + "/sync-all", {}).then(hydrateRoute); return; }
|
if (action === "sync-all") { adminPost(api.realnameBase + "/sync-all", {}).then(hydrateRoute); return; }
|
||||||
@@ -218,6 +259,22 @@
|
|||||||
adminPost(api.adminBase + "/user/update", serializeForm(form)).then(() => { state.modal = null; hydrateRoute(); });
|
adminPost(api.adminBase + "/user/update", serializeForm(form)).then(() => { state.modal = null; hydrateRoute(); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (action === "config-save") {
|
||||||
|
adminPost(api.adminBase + "/config/save", serializeForm(form)).then(() => { hydrateRoute(); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "node-save") {
|
||||||
|
adminPost(api.adminBase + "/server/manage/save", serializeForm(form)).then(() => { state.modal = null; hydrateRoute(); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "group-save") {
|
||||||
|
adminPost(api.adminBase + "/server/group/save", serializeForm(form)).then(() => { state.modal = null; hydrateRoute(); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "route-save") {
|
||||||
|
adminPost(api.adminBase + "/server/route/save", serializeForm(form)).then(() => { state.modal = null; hydrateRoute(); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -241,6 +298,9 @@
|
|||||||
if (m.type === "plan") html += renderPlanForm(m.data);
|
if (m.type === "plan") html += renderPlanForm(m.data);
|
||||||
if (m.type === "user") html += renderUserForm(m.data);
|
if (m.type === "user") html += renderUserForm(m.data);
|
||||||
if (m.type === "coupon") html += renderCouponForm(m.data);
|
if (m.type === "coupon") html += renderCouponForm(m.data);
|
||||||
|
if (m.type === "node") html += renderNodeForm(m.data);
|
||||||
|
if (m.type === "group") html += renderGroupForm(m.data);
|
||||||
|
if (m.type === "route") html += renderRouteForm(m.data);
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -405,6 +465,7 @@
|
|||||||
if (route === "coupon-manage") return renderCouponManage();
|
if (route === "coupon-manage") return renderCouponManage();
|
||||||
if (route === "user-manage") return renderUserManage();
|
if (route === "user-manage") return renderUserManage();
|
||||||
if (route === "ticket-manage") return renderTicketManage();
|
if (route === "ticket-manage") return renderTicketManage();
|
||||||
|
if (route === "system-config") return renderSystemConfig();
|
||||||
if (route === "realname") return renderRealName();
|
if (route === "realname") return renderRealName();
|
||||||
if (route === "user-online-devices") return renderOnlineDevices();
|
if (route === "user-online-devices") return renderOnlineDevices();
|
||||||
return renderOverview();
|
return renderOverview();
|
||||||
@@ -536,7 +597,7 @@
|
|||||||
function renderNodeManage() {
|
function renderNodeManage() {
|
||||||
var rows = state.nodes || [];
|
var rows = state.nodes || [];
|
||||||
return [
|
return [
|
||||||
'<div class="toolbar" style="margin-bottom:24px;"><button class="btn btn-primary">Add New Node</button></div>',
|
'<div class="toolbar" style="margin-bottom:24px;"><button class="btn btn-primary" data-action="node-add">Add New Node</button></div>',
|
||||||
'<div class="table-wrap glass-card">',
|
'<div class="table-wrap glass-card">',
|
||||||
renderTable(["ID", "Name/Host", "Type", "Rate", "Visibility", "Actions"], rows.map(function(row) {
|
renderTable(["ID", "Name/Host", "Type", "Rate", "Visibility", "Actions"], rows.map(function(row) {
|
||||||
return [
|
return [
|
||||||
@@ -545,7 +606,7 @@
|
|||||||
escapeHtml(row.type),
|
escapeHtml(row.type),
|
||||||
'<strong>' + row.rate + 'x</strong>',
|
'<strong>' + row.rate + 'x</strong>',
|
||||||
renderStatus(row.show ? "visible" : "hidden"),
|
renderStatus(row.show ? "visible" : "hidden"),
|
||||||
'<div class="row-actions"><button class="btn btn-ghost" data-action="node-edit" data-id="' + row.id + '">Edit</button><button class="btn btn-ghost" data-action="node-copy" data-id="' + row.id + '">Copy</button></div>'
|
'<div class="row-actions"><button class="btn btn-ghost" data-action="node-edit" data-id="' + row.id + '">Edit</button><button class="btn btn-ghost" data-action="node-copy" data-id="' + row.id + '">Copy</button><button class="btn btn-ghost" data-action="node-delete" data-id="' + row.id + '">Delete</button></div>'
|
||||||
];
|
];
|
||||||
})),
|
})),
|
||||||
'</div>'
|
'</div>'
|
||||||
@@ -587,6 +648,72 @@
|
|||||||
].join("");
|
].join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderNodeGroup() {
|
||||||
|
var rows = state.groups || [];
|
||||||
|
return [
|
||||||
|
'<div class="toolbar" style="margin-bottom:24px;"><button class="btn btn-primary" data-action="group-add">Add Group</button></div>',
|
||||||
|
'<div class="table-wrap glass-card">',
|
||||||
|
renderTable(["ID", "Group Name", "Node Count", "User Count", "Actions"], rows.map(function(row) {
|
||||||
|
return [
|
||||||
|
'<code>' + row.id + '</code>',
|
||||||
|
'<strong>' + escapeHtml(row.name) + '</strong>',
|
||||||
|
'<code>' + (row.server_count || 0) + '</code>',
|
||||||
|
'<code>' + (row.user_count || 0) + '</code>',
|
||||||
|
'<div class="row-actions"><button class="btn btn-ghost" data-action="group-edit" data-id="' + row.id + '">Edit</button><button class="btn btn-ghost" data-action="group-delete" data-id="' + row.id + '">Delete</button></div>'
|
||||||
|
];
|
||||||
|
})),
|
||||||
|
'</div>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNodeRoute() {
|
||||||
|
var rows = state.routes || [];
|
||||||
|
return [
|
||||||
|
'<div class="toolbar" style="margin-bottom:24px;"><button class="btn btn-primary" data-action="route-add">Add Route</button></div>',
|
||||||
|
'<div class="table-wrap glass-card">',
|
||||||
|
renderTable(["ID", "Remarks", "Match", "Action", "Actions"], rows.map(function(row) {
|
||||||
|
return [
|
||||||
|
'<code>' + row.id + '</code>',
|
||||||
|
'<strong>' + escapeHtml(row.remarks) + '</strong>',
|
||||||
|
'<div style="font-size:12px;">' + (row.match || []).join(", ") + '</div>',
|
||||||
|
'<code>' + row.action + '</code>',
|
||||||
|
'<div class="row-actions"><button class="btn btn-ghost" data-action="route-edit" data-id="' + row.id + '">Edit</button><button class="btn btn-ghost" data-action="route-delete" data-id="' + row.id + '">Delete</button></div>'
|
||||||
|
];
|
||||||
|
})),
|
||||||
|
'</div>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSystemConfig() {
|
||||||
|
var cfg = state.config || {};
|
||||||
|
var tab = state.configTab || "site";
|
||||||
|
var sections = ["site", "subscribe", "server", "email", "telegram", "safe"];
|
||||||
|
|
||||||
|
var tabData = cfg[tab] || {};
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<div class="tabs-nav glass-card" style="margin-bottom:24px; padding:8px; display:flex; gap:8px; overflow-x:auto;">',
|
||||||
|
sections.map(s => '<button class="btn ' + (tab === s ? "btn-primary" : "btn-ghost") + '" data-action="config-tab" data-tab="' + s + '">' + s.toUpperCase() + '</button>').join(""),
|
||||||
|
'</div>',
|
||||||
|
'<form data-form="config-save" class="glass-card card fade-in" style="padding:40px;">',
|
||||||
|
'<div class="grid grid-2">',
|
||||||
|
Object.keys(tabData).map(key => {
|
||||||
|
var val = tabData[key];
|
||||||
|
var label = key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
var input = '<input name="' + key + '" value="' + (val === null ? "" : val) + '" />';
|
||||||
|
if (typeof val === "boolean") {
|
||||||
|
input = '<select name="' + key + '"><option value="1" ' + (val ? "selected" : "") + '>Enabled</option><option value="0" ' + (val ? "" : "selected") + '>Disabled</option></select>';
|
||||||
|
}
|
||||||
|
return '<div class="field"><label>' + label + '</label>' + input + '</div>';
|
||||||
|
}).join(""),
|
||||||
|
'</div>',
|
||||||
|
'<div style="margin-top:32px; border-top:1px solid var(--border); padding-top:32px;">',
|
||||||
|
'<button class="btn btn-primary" type="submit">Apply System Settings</button>',
|
||||||
|
'</div>',
|
||||||
|
'</form>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
function statCard(title, value, hint) {
|
function statCard(title, value, hint) {
|
||||||
return [
|
return [
|
||||||
@@ -693,6 +820,48 @@
|
|||||||
});
|
});
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
function renderNodeForm(d) {
|
||||||
|
return [
|
||||||
|
'<h2>' + (d.id ? "Edit Node" : "Create Node") + '</h2>',
|
||||||
|
'<form data-form="node-save" style="max-height:70vh; overflow-y:auto; padding-right:12px;">',
|
||||||
|
'<input type="hidden" name="id" value="' + (d.id || "") + '" />',
|
||||||
|
'<div class="grid grid-2">',
|
||||||
|
'<div class="field"><label>Node Name</label><input name="name" value="' + (d.name || "") + '" required /></div>',
|
||||||
|
'<div class="field"><label>Node Type</label><input name="type" value="' + (d.type || "shadowsocks") + '" required /></div>',
|
||||||
|
'<div class="field"><label>Server Domain/IP</label><input name="host" value="' + (d.host || "") + '" required /></div>',
|
||||||
|
'<div class="field"><label>Public Port</label><input name="port" value="' + (d.port || "") + '" required /></div>',
|
||||||
|
'<div class="field"><label>Internal Port</label><input type="number" name="server_port" value="' + (d.server_port || 443) + '" required /></div>',
|
||||||
|
'<div class="field"><label>Rate multiplier</label><input type="number" step="0.1" name="rate" value="' + (d.rate || 1.0) + '" /></div>',
|
||||||
|
'</div>',
|
||||||
|
'<div class="field"><label>Visibility</label><select name="show"><option value="1">Visible</option><option value="0" ' + (d.show ? "" : "selected") + '>Hidden</option></select></div>',
|
||||||
|
'<button class="btn btn-primary" type="submit">Save Node</button>',
|
||||||
|
'</form>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGroupForm(d) {
|
||||||
|
return [
|
||||||
|
'<h2>' + (d.id ? "Edit Group" : "Create Group") + '</h2>',
|
||||||
|
'<form data-form="group-save">',
|
||||||
|
'<input type="hidden" name="id" value="' + (d.id || "") + '" />',
|
||||||
|
'<div class="field"><label>Group Name</label><input name="name" value="' + (d.name || "") + '" required /></div>',
|
||||||
|
'<button class="btn btn-primary" type="submit">Save Group</button>',
|
||||||
|
'</form>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRouteForm(d) {
|
||||||
|
return [
|
||||||
|
'<h2>' + (d.id ? "Edit Route" : "Create Route") + '</h2>',
|
||||||
|
'<form data-form="route-save">',
|
||||||
|
'<input type="hidden" name="id" value="' + (d.id || "") + '" />',
|
||||||
|
'<div class="field"><label>Remarks</label><input name="remarks" value="' + (d.remarks || "") + '" required /></div>',
|
||||||
|
'<div class="field"><label>Action</label><select name="action"><option value="direct">Direct</option><option value="proxy">Proxy</option><option value="block">Block</option></select></div>',
|
||||||
|
'<button class="btn btn-primary" type="submit">Save Route</button>',
|
||||||
|
'</form>'
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
function getRouteTitle(r) { return r.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()); }
|
function getRouteTitle(r) { return r.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()); }
|
||||||
function getRouteDescription(r) { return "SingBox Gopanel integrated module management."; }
|
function getRouteDescription(r) { return "SingBox Gopanel integrated module management."; }
|
||||||
|
|
||||||
|
|||||||
@@ -234,3 +234,36 @@ func loadTicketMessageCountMap(ticketIDs []int) map[int]int64 {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePositiveInt(raw string, defaultValue int) int {
|
||||||
|
value, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||||
|
if err != nil || value <= 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateLastPage(total int64, perPage int) int {
|
||||||
|
if perPage <= 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
last := int((total + int64(perPage) - 1) / int64(perPage))
|
||||||
|
if last == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUnixValue(value int64) string {
|
||||||
|
if value <= 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTimeValue(value *time.Time) string {
|
||||||
|
if value == nil {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return value.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"xboard-go/internal/database"
|
"xboard-go/internal/database"
|
||||||
@@ -198,11 +197,101 @@ func PluginUserAddIPv6SyncPassword(c *gin.Context) {
|
|||||||
|
|
||||||
func AdminConfigFetch(c *gin.Context) {
|
func AdminConfigFetch(c *gin.Context) {
|
||||||
Success(c, gin.H{
|
Success(c, gin.H{
|
||||||
|
"invite": gin.H{
|
||||||
|
"invite_force": service.MustGetBool("invite_force", false),
|
||||||
|
"invite_commission": service.MustGetInt("invite_commission", 10),
|
||||||
|
"invite_gen_limit": service.MustGetInt("invite_gen_limit", 5),
|
||||||
|
"invite_never_expire": service.MustGetBool("invite_never_expire", false),
|
||||||
|
"commission_first_time_enable": service.MustGetBool("commission_first_time_enable", true),
|
||||||
|
"commission_auto_check_enable": service.MustGetBool("commission_auto_check_enable", true),
|
||||||
|
"commission_withdraw_limit": service.MustGetInt("commission_withdraw_limit", 100),
|
||||||
|
"withdraw_close_enable": service.MustGetBool("withdraw_close_enable", false),
|
||||||
|
"commission_distribution_enable": service.MustGetBool("commission_distribution_enable", false),
|
||||||
|
"commission_distribution_l1": service.MustGetInt("commission_distribution_l1", 0),
|
||||||
|
"commission_distribution_l2": service.MustGetInt("commission_distribution_l2", 0),
|
||||||
|
"commission_distribution_l3": service.MustGetInt("commission_distribution_l3", 0),
|
||||||
|
},
|
||||||
|
"site": gin.H{
|
||||||
|
"logo": service.MustGetString("logo", ""),
|
||||||
|
"force_https": service.MustGetBool("force_https", false),
|
||||||
|
"stop_register": service.MustGetBool("stop_register", false),
|
||||||
"app_name": service.MustGetString("app_name", "XBoard"),
|
"app_name": service.MustGetString("app_name", "XBoard"),
|
||||||
|
"app_description": service.MustGetString("app_description", "XBoard is best!"),
|
||||||
"app_url": service.GetAppURL(),
|
"app_url": service.GetAppURL(),
|
||||||
"secure_path": service.GetAdminSecurePath(),
|
"subscribe_url": service.MustGetString("subscribe_url", ""),
|
||||||
|
"try_out_enable": service.MustGetBool("try_out_enable", false),
|
||||||
|
"try_out_plan_id": service.MustGetInt("try_out_plan_id", 0),
|
||||||
|
"try_out_hour": service.MustGetInt("try_out_hour", 1),
|
||||||
|
"tos_url": service.MustGetString("tos_url", ""),
|
||||||
|
"currency": service.MustGetString("currency", "CNY"),
|
||||||
|
"currency_symbol": service.MustGetString("currency_symbol", "¥"),
|
||||||
|
"ticket_must_wait_reply": service.MustGetBool("ticket_must_wait_reply", false),
|
||||||
|
},
|
||||||
|
"subscribe": gin.H{
|
||||||
|
"plan_change_enable": service.MustGetBool("plan_change_enable", true),
|
||||||
|
"reset_traffic_method": service.MustGetInt("reset_traffic_method", 0),
|
||||||
|
"surplus_enable": service.MustGetBool("surplus_enable", true),
|
||||||
|
"new_order_event_id": service.MustGetInt("new_order_event_id", 0),
|
||||||
|
"renew_order_event_id": service.MustGetInt("renew_order_event_id", 0),
|
||||||
|
"change_order_event_id": service.MustGetInt("change_order_event_id", 0),
|
||||||
|
"show_info_to_server_enable": service.MustGetBool("show_info_to_server_enable", false),
|
||||||
|
"show_protocol_to_server_enable": service.MustGetBool("show_protocol_to_server_enable", false),
|
||||||
|
"subscribe_path": service.MustGetString("subscribe_path", "s"),
|
||||||
|
},
|
||||||
|
"frontend": gin.H{
|
||||||
|
"frontend_theme": service.MustGetString("frontend_theme", "Xboard"),
|
||||||
|
"frontend_theme_sidebar": service.MustGetString("frontend_theme_sidebar", "light"),
|
||||||
|
"frontend_theme_header": service.MustGetString("frontend_theme_header", "dark"),
|
||||||
|
"frontend_theme_color": service.MustGetString("frontend_theme_color", "default"),
|
||||||
|
"frontend_background_url": service.MustGetString("frontend_background_url", ""),
|
||||||
|
},
|
||||||
|
"server": gin.H{
|
||||||
|
"server_token": service.MustGetString("server_token", ""),
|
||||||
"server_pull_interval": service.MustGetInt("server_pull_interval", 60),
|
"server_pull_interval": service.MustGetInt("server_pull_interval", 60),
|
||||||
"server_push_interval": service.MustGetInt("server_push_interval", 60),
|
"server_push_interval": service.MustGetInt("server_push_interval", 60),
|
||||||
|
"device_limit_mode": service.MustGetInt("device_limit_mode", 0),
|
||||||
|
"server_ws_enable": service.MustGetBool("server_ws_enable", true),
|
||||||
|
"server_ws_url": service.MustGetString("server_ws_url", ""),
|
||||||
|
},
|
||||||
|
"email": gin.H{
|
||||||
|
"email_template": service.MustGetString("email_template", "default"),
|
||||||
|
"email_host": service.MustGetString("email_host", ""),
|
||||||
|
"email_port": service.MustGetString("email_port", ""),
|
||||||
|
"email_username": service.MustGetString("email_username", ""),
|
||||||
|
"email_password": service.MustGetString("email_password", ""),
|
||||||
|
"email_encryption": service.MustGetString("email_encryption", ""),
|
||||||
|
"email_from_address": service.MustGetString("email_from_address", ""),
|
||||||
|
"remind_mail_enable": service.MustGetBool("remind_mail_enable", false),
|
||||||
|
},
|
||||||
|
"telegram": gin.H{
|
||||||
|
"telegram_bot_enable": service.MustGetBool("telegram_bot_enable", false),
|
||||||
|
"telegram_bot_token": service.MustGetString("telegram_bot_token", ""),
|
||||||
|
"telegram_webhook_url": service.MustGetString("telegram_webhook_url", ""),
|
||||||
|
"telegram_discuss_link": service.MustGetString("telegram_discuss_link", ""),
|
||||||
|
},
|
||||||
|
"app": gin.H{
|
||||||
|
"windows_version": service.MustGetString("windows_version", ""),
|
||||||
|
"windows_download_url": service.MustGetString("windows_download_url", ""),
|
||||||
|
"macos_version": service.MustGetString("macos_version", ""),
|
||||||
|
"macos_download_url": service.MustGetString("macos_download_url", ""),
|
||||||
|
"android_version": service.MustGetString("android_version", ""),
|
||||||
|
"android_download_url": service.MustGetString("android_download_url", ""),
|
||||||
|
},
|
||||||
|
"safe": gin.H{
|
||||||
|
"email_verify": service.MustGetBool("email_verify", false),
|
||||||
|
"safe_mode_enable": service.MustGetBool("safe_mode_enable", false),
|
||||||
|
"secure_path": service.GetAdminSecurePath(),
|
||||||
|
"email_whitelist_enable": service.MustGetBool("email_whitelist_enable", false),
|
||||||
|
"email_whitelist_suffix": service.MustGetString("email_whitelist_suffix", ""),
|
||||||
|
"email_gmail_limit_enable": service.MustGetBool("email_gmail_limit_enable", false),
|
||||||
|
"captcha_enable": service.MustGetBool("captcha_enable", false),
|
||||||
|
"register_limit_by_ip_enable": service.MustGetBool("register_limit_by_ip_enable", false),
|
||||||
|
"register_limit_count": service.MustGetInt("register_limit_count", 3),
|
||||||
|
"register_limit_expire": service.MustGetInt("register_limit_expire", 60),
|
||||||
|
"password_limit_enable": service.MustGetBool("password_limit_enable", true),
|
||||||
|
"password_limit_count": service.MustGetInt("password_limit_count", 5),
|
||||||
|
"password_limit_expire": service.MustGetInt("password_limit_expire", 60),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,39 +300,11 @@ func AdminSystemStatus(c *gin.Context) {
|
|||||||
"server_time": time.Now().Unix(),
|
"server_time": time.Now().Unix(),
|
||||||
"app_name": service.MustGetString("app_name", "XBoard"),
|
"app_name": service.MustGetString("app_name", "XBoard"),
|
||||||
"app_url": service.GetAppURL(),
|
"app_url": service.GetAppURL(),
|
||||||
|
"version": "1.0.0",
|
||||||
|
"go_version": "1.21+",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func parsePositiveInt(raw string, defaultValue int) int {
|
|
||||||
value, err := strconv.Atoi(strings.TrimSpace(raw))
|
|
||||||
if err != nil || value <= 0 {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateLastPage(total int64, perPage int) int {
|
|
||||||
if perPage <= 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
last := int((total + int64(perPage) - 1) / int64(perPage))
|
|
||||||
if last == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return last
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatUnixValue(value int64) string {
|
|
||||||
if value <= 0 {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatTimeValue(value *time.Time) string {
|
|
||||||
if value == nil {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return value.Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user