Some checks failed
CI / Lint + Build (push) Has been cancelled
审计修复: - CSP: unsafe-inline移除, nonce替代; unsafe-eval保留供Vditor使用 - HSTS: 始终启用(不再依赖release模式) - Controller Exp: common.GetGinExp包装, 不再直接读context - UID解析: common.ParseUIDParam统一, follow/admin controller改用 重构: - router/api.go(198行)拆为7个域文件: auth/settings/posts/comments/reactions/social/studio - 管理后台站点设置: 单页拆为4个子页(brand/security/registration/content)+子菜单 - 前台用户设置页: 1201行拆为6个独立模板+6个独立JS文件 新增: - DB健康检测中间件(middleware/db_health.go): 5s ping+降级页面 - 时区配置: config.yaml server.timezone→time.Local初始化 - .gitea/workflows/ci.yml: strict模式CI流水线
152 lines
6.9 KiB
JavaScript
152 lines
6.9 KiB
JavaScript
(function(){'use strict';
|
|
<script nonce="{{.CSPNonce}}">
|
|
(function () {
|
|
'use strict';
|
|
|
|
var toastEl = document.getElementById('settingsToast');
|
|
var toastTimer = null;
|
|
|
|
function showSettingsToast(msg, isError) {
|
|
if (!toastEl) return;
|
|
clearTimeout(toastTimer);
|
|
toastEl.textContent = msg;
|
|
toastEl.className = 'settings-toast' + (isError ? ' error' : '');
|
|
void toastEl.offsetWidth;
|
|
toastEl.classList.add('show');
|
|
toastTimer = setTimeout(function () {
|
|
toastEl.classList.remove('show');
|
|
}, 5000);
|
|
}
|
|
// ===== 登录管理 Tab =====
|
|
(function () {
|
|
var kickBtns = document.querySelectorAll('.session-kick-btn');
|
|
var kickOthersBtn = document.getElementById('kickOthersBtn');
|
|
var remarkInputs = document.querySelectorAll('.session-remark-input');
|
|
if (!kickBtns.length && !kickOthersBtn && !remarkInputs.length) return;
|
|
|
|
// ---- 踢出单个设备 ----
|
|
kickBtns.forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var sid = btn.getAttribute('data-sid');
|
|
if (!sid) return;
|
|
showConfirm('确认', '确定要踢出该设备吗?').then(function(ok) {
|
|
if (!ok) return;
|
|
btn.disabled = true;
|
|
btn.textContent = '踢出中...';
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('DELETE', '/api/settings/sessions/' + encodeURIComponent(sid), true);
|
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState !== 4) return;
|
|
btn.disabled = false;
|
|
btn.textContent = '踢出';
|
|
try {
|
|
var res = JSON.parse(xhr.responseText);
|
|
if (res.success) {
|
|
showSettingsToast(res.message || '已踢出该设备');
|
|
var item = btn.closest('.session-item');
|
|
if (item) {
|
|
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
|
item.style.opacity = '0';
|
|
item.style.transform = 'translateX(20px)';
|
|
setTimeout(function () { item.remove(); }, 300);
|
|
}
|
|
} else {
|
|
showSettingsToast(res.message || '操作失败', true);
|
|
}
|
|
} catch (e) {
|
|
showSettingsToast('操作失败', true);
|
|
}
|
|
};
|
|
xhr.send();
|
|
});
|
|
});
|
|
});
|
|
|
|
// ---- 一键踢出其他设备 ----
|
|
if (kickOthersBtn) {
|
|
kickOthersBtn.addEventListener('click', function () {
|
|
var otherCount = document.querySelectorAll('.session-item:not(.session-current)').length;
|
|
if (otherCount === 0) {
|
|
showSettingsToast('没有其他在线设备');
|
|
return;
|
|
}
|
|
showConfirm('确认', '确定要踢出所有其他设备(共 ' + otherCount + ' 台)吗?').then(function(ok) {
|
|
if (!ok) return;
|
|
kickOthersBtn.disabled = true;
|
|
kickOthersBtn.textContent = '处理中...';
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/api/settings/sessions/destroy-others', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState !== 4) return;
|
|
kickOthersBtn.disabled = false;
|
|
kickOthersBtn.textContent = '一键踢出其他设备';
|
|
try {
|
|
var res = JSON.parse(xhr.responseText);
|
|
if (res.success) {
|
|
showSettingsToast(res.message || '已踢出所有其他设备');
|
|
var others = document.querySelectorAll('.session-item:not(.session-current)');
|
|
others.forEach(function (item, i) {
|
|
setTimeout(function () {
|
|
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
|
item.style.opacity = '0';
|
|
item.style.transform = 'translateX(20px)';
|
|
setTimeout(function () { item.remove(); }, 300);
|
|
}, i * 100);
|
|
});
|
|
} else {
|
|
showSettingsToast(res.message || '操作失败', true);
|
|
}
|
|
} catch (e) {
|
|
showSettingsToast('操作失败', true);
|
|
}
|
|
};
|
|
xhr.send();
|
|
});
|
|
});
|
|
}
|
|
|
|
// ---- 备注保存 ----
|
|
var remarkTimers = {};
|
|
remarkInputs.forEach(function (input) {
|
|
input.addEventListener('blur', function () {
|
|
var sid = input.getAttribute('data-sid');
|
|
var remark = input.value.trim();
|
|
if (!sid) return;
|
|
saveRemark(sid, remark);
|
|
});
|
|
input.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
input.blur();
|
|
}
|
|
});
|
|
});
|
|
|
|
function saveRemark(sid, remark) {
|
|
if (remarkTimers[sid]) clearTimeout(remarkTimers[sid]);
|
|
remarkTimers[sid] = setTimeout(function () {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('PUT', '/api/settings/sessions/' + encodeURIComponent(sid) + '/remark', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState !== 4) return;
|
|
try {
|
|
var res = JSON.parse(xhr.responseText);
|
|
if (res.success) {
|
|
showSettingsToast('备注已保存');
|
|
} else {
|
|
showSettingsToast(res.message || '保存失败', true);
|
|
}
|
|
} catch (e) {
|
|
showSettingsToast('保存失败', true);
|
|
}
|
|
};
|
|
xhr.send(JSON.stringify({ remark: remark }));
|
|
}, 500);
|
|
}
|
|
})();
|
|
})();
|