Files

840 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const state = {
bootstrap: window.APP_BOOTSTRAP || {},
authenticated: false,
users: [],
page: 1,
pageSize: (window.APP_BOOTSTRAP && window.APP_BOOTSTRAP.pageSize) || 25,
total: 0,
totalBeforeSearch: 0,
summary: { active: 0, disabled: 0 },
search: "",
licenses: [],
selectedUser: null,
selectedUsers: new Set(),
activeTaskId: "",
activeTaskTimer: null,
};
const elements = {
platformStatus: document.getElementById("platform-status"),
platformSubstatus: document.getElementById("platform-substatus"),
metricTotal: document.getElementById("metric-total"),
metricActive: document.getElementById("metric-active"),
metricDisabled: document.getElementById("metric-disabled"),
metricLicense: document.getElementById("metric-license"),
usersTableBody: document.getElementById("users-table-body"),
paginationInfo: document.getElementById("pagination-info"),
licenseList: document.getElementById("license-list"),
resultConsole: document.getElementById("result-console"),
loginSection: document.getElementById("login-section"),
loginForm: document.getElementById("login-form"),
logoutBtn: document.getElementById("logout-btn"),
refreshAllBtn: document.getElementById("refresh-all-btn"),
searchForm: document.getElementById("search-form"),
searchInput: document.getElementById("search-input"),
prevPageBtn: document.getElementById("prev-page-btn"),
nextPageBtn: document.getElementById("next-page-btn"),
userForm: document.getElementById("user-form"),
selectedUserId: document.getElementById("selected-user-id"),
deleteUserBtn: document.getElementById("delete-user-btn"),
newUserBtn: document.getElementById("new-user-btn"),
clearFormBtn: document.getElementById("clear-form-btn"),
resetPasswordBtn: document.getElementById("reset-password-btn"),
selectedCount: document.getElementById("selected-count"),
selectAllResultsBtn: document.getElementById("select-all-results-btn"),
clearSelectionBtn: document.getElementById("clear-selection-btn"),
bulkEnableBtn: document.getElementById("bulk-enable-btn"),
bulkDisableBtn: document.getElementById("bulk-disable-btn"),
bulkResetBtn: document.getElementById("bulk-reset-btn"),
bulkDeleteBtn: document.getElementById("bulk-delete-btn"),
selectPageCheckbox: document.getElementById("select-page-checkbox"),
taskStatusCard: document.getElementById("task-status-card"),
taskStatusTitle: document.getElementById("task-status-title"),
taskStatusState: document.getElementById("task-status-state"),
taskProgressFill: document.getElementById("task-progress-fill"),
taskStatusText: document.getElementById("task-status-text"),
};
const TASK_LABELS = {
"create": "批量创建",
"update": "批量更新",
"delete": "批量删除",
"reset-password": "批量改密",
};
async function api(path, options = {}) {
const config = { ...options };
config.headers = config.headers || {};
if (config.body && !(config.body instanceof FormData)) {
config.headers["Content-Type"] = "application/json";
}
const response = await fetch(path, config);
const payload = await response.json().catch(() => ({
success: false,
message: "接口返回了无法解析的响应。",
}));
if (response.status === 401) {
state.authenticated = false;
updateAuthView();
}
if (!response.ok || !payload.success) {
throw new Error(payload.message || "请求失败");
}
return payload.data;
}
function updatePlatformStatus() {
if (state.bootstrap.graphReady) {
elements.platformStatus.textContent = "Graph 已就绪";
elements.platformSubstatus.textContent = state.bootstrap.graphFlavor || "Microsoft Graph Global";
} else {
elements.platformStatus.textContent = "等待配置";
const errors = state.bootstrap.validationErrors || [];
elements.platformSubstatus.textContent = errors.join("") || "请补充 .env";
}
}
function updateAuthView() {
const needsLogin = state.bootstrap.authEnabled && !state.authenticated;
elements.loginSection.classList.toggle("hidden", !needsLogin);
}
function setConsole(content) {
elements.resultConsole.textContent = content;
}
function appendConsole(title, summary) {
const lines = [
`[${new Date().toLocaleString("zh-CN")}] ${title}`,
String(summary || ""),
"",
];
elements.resultConsole.textContent = lines.join("\n") + elements.resultConsole.textContent;
}
function formatTaskLabel(operation) {
return TASK_LABELS[operation] || operation;
}
function formatTaskState(task) {
if (task.status === "queued") {
return "已提交";
}
if (task.status === "running") {
return "执行中";
}
if (task.status === "succeeded") {
return "已完成";
}
if (task.status === "failed") {
return "失败";
}
return task.status || "未知";
}
function summarizeTask(task) {
const base = `${formatTaskLabel(task.operation)}${task.completed}/${task.total},成功 ${task.successCount},失败 ${task.failureCount}`;
if (task.failureCount > 0) {
return `${base}。详细失败项请查看 logs/office365_admin.log`;
}
return base;
}
function showTaskCard(task) {
if (!elements.taskStatusCard) {
return;
}
elements.taskStatusCard.classList.remove("hidden");
elements.taskStatusTitle.textContent = formatTaskLabel(task.operation);
elements.taskStatusState.textContent = formatTaskState(task);
elements.taskProgressFill.style.width = `${task.progressPercent || 0}%`;
const statusText = [
`进度 ${task.completed || 0} / ${task.total || 0}`,
`成功 ${task.successCount || 0}`,
`失败 ${task.failureCount || 0}`,
].join(" · ");
const currentText = task.currentItem
? ` · 当前 ${task.currentItem}`
: "";
elements.taskStatusText.textContent = `${statusText}${currentText}`;
}
function showSingleActionStatus(title, text) {
if (!elements.taskStatusCard) {
return;
}
elements.taskStatusCard.classList.remove("hidden");
elements.taskStatusTitle.textContent = title;
elements.taskStatusState.textContent = "处理中";
elements.taskProgressFill.style.width = "30%";
elements.taskStatusText.textContent = text;
}
function completeSingleActionStatus(title, text) {
if (!elements.taskStatusCard) {
return;
}
elements.taskStatusCard.classList.remove("hidden");
elements.taskStatusTitle.textContent = title;
elements.taskStatusState.textContent = "已完成";
elements.taskProgressFill.style.width = "100%";
elements.taskStatusText.textContent = text;
}
function showFailedActionStatus(title, text) {
if (!elements.taskStatusCard) {
return;
}
elements.taskStatusCard.classList.remove("hidden");
elements.taskStatusTitle.textContent = title;
elements.taskStatusState.textContent = "失败";
elements.taskProgressFill.style.width = "100%";
elements.taskStatusText.textContent = text;
}
function clearTaskPolling() {
if (state.activeTaskTimer) {
window.clearTimeout(state.activeTaskTimer);
state.activeTaskTimer = null;
}
}
async function pollTask(taskId) {
try {
const task = await api(`/api/tasks/${encodeURIComponent(taskId)}`);
showTaskCard(task);
if (task.status === "queued" || task.status === "running") {
clearTaskPolling();
state.activeTaskTimer = window.setTimeout(() => pollTask(taskId), 1000);
return;
}
state.activeTaskId = "";
clearTaskPolling();
appendConsole(
task.status === "failed" ? `${formatTaskLabel(task.operation)}失败` : `${formatTaskLabel(task.operation)}完成`,
summarizeTask(task),
);
await refreshAll();
} catch (error) {
state.activeTaskId = "";
clearTaskPolling();
showFailedActionStatus("任务轮询失败", error.message);
appendConsole("任务轮询失败", error.message);
}
}
function startTask(task, submittedMessage) {
state.activeTaskId = task.id;
clearTaskPolling();
showTaskCard(task);
appendConsole("任务已提交", submittedMessage);
pollTask(task.id);
}
function statusPill(enabled) {
const klass = enabled ? "active" : "disabled";
const label = enabled ? "启用" : "停用";
return `<span class="status-pill ${klass}">${label}</span>`;
}
function selectedIdentifiers() {
return Array.from(state.selectedUsers);
}
function updateSelectionUi() {
const count = state.selectedUsers.size;
if (elements.selectedCount) {
elements.selectedCount.textContent = count ? `已选择 ${count} 个账号` : "未选择账号";
}
const currentPageIds = state.users.map((user) => user.userPrincipalName).filter(Boolean);
const selectedOnPage = currentPageIds.filter((identifier) => state.selectedUsers.has(identifier)).length;
if (elements.selectPageCheckbox) {
elements.selectPageCheckbox.checked = currentPageIds.length > 0 && selectedOnPage === currentPageIds.length;
elements.selectPageCheckbox.indeterminate = selectedOnPage > 0 && selectedOnPage < currentPageIds.length;
elements.selectPageCheckbox.disabled = currentPageIds.length === 0;
}
[
elements.clearSelectionBtn,
elements.bulkEnableBtn,
elements.bulkDisableBtn,
elements.bulkResetBtn,
elements.bulkDeleteBtn,
].forEach((button) => {
if (button) {
button.disabled = count === 0;
}
});
if (elements.selectAllResultsBtn) {
elements.selectAllResultsBtn.disabled = state.total === 0;
}
}
function renderUsers() {
if (!state.users.length) {
elements.usersTableBody.innerHTML = `<tr><td colspan="7" class="empty-card">没有匹配到用户</td></tr>`;
updateSelectionUi();
return;
}
elements.usersTableBody.innerHTML = state.users.map((user) => {
const identifier = user.userPrincipalName || "";
const checked = state.selectedUsers.has(identifier) ? "checked" : "";
return `
<tr>
<td class="check-cell">
<input type="checkbox" data-select-identifier="${encodeURIComponent(identifier)}" ${checked}>
</td>
<td>${escapeHtml(user.displayName || "-")}</td>
<td>${escapeHtml(identifier || "-")}</td>
<td>
<div>${escapeHtml(user.department || "-")}</div>
<div class="muted">${escapeHtml(user.jobTitle || "-")}</div>
</td>
<td>${statusPill(user.accountEnabled)}</td>
<td>${user.assignedLicensesCount || 0}</td>
<td>
<div class="actions-inline">
<button class="btn btn-secondary" data-action="view" data-identifier="${encodeURIComponent(identifier)}">查看</button>
<button class="btn btn-ghost" data-action="reset" data-identifier="${encodeURIComponent(identifier)}">改密</button>
<button class="btn btn-danger" data-action="delete" data-identifier="${encodeURIComponent(identifier)}">删除</button>
</div>
</td>
</tr>
`;
}).join("");
elements.paginationInfo.textContent = `${state.page} 页,当前 ${state.users.length} / 共 ${state.total}`;
updateSelectionUi();
}
function renderMetrics() {
elements.metricTotal.textContent = String(state.total);
elements.metricActive.textContent = String(state.summary.active || 0);
elements.metricDisabled.textContent = String(state.summary.disabled || 0);
const available = state.licenses.reduce((sum, item) => sum + (item.availableUnits || 0), 0);
elements.metricLicense.textContent = String(available);
const totalFoot = state.search
? `搜索结果 ${state.total} / 全量 ${state.totalBeforeSearch}`
: `全量账号 ${state.totalBeforeSearch}`;
document.getElementById("metric-total-foot").textContent = totalFoot;
}
function renderLicenses() {
if (!state.licenses.length) {
elements.licenseList.innerHTML = `<div class="empty-card">未获取到许可证信息</div>`;
return;
}
elements.licenseList.innerHTML = state.licenses.map((item) => `
<article class="license-card">
<strong>${escapeHtml(item.skuPartNumber || "UNKNOWN")}</strong>
<div>可用席位:${item.availableUnits}</div>
<div>已用席位:${item.consumedUnits}</div>
<div>总席位:${item.totalUnits}</div>
</article>
`).join("");
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
async function fetchSession() {
const data = await api("/api/session");
state.authenticated = data.authenticated;
updateAuthView();
}
async function fetchUsers() {
const query = new URLSearchParams({
page: String(state.page),
pageSize: String(state.pageSize),
search: state.search,
});
const data = await api(`/api/users?${query.toString()}`);
state.users = data.items;
state.total = data.total;
state.totalBeforeSearch = data.totalBeforeSearch;
state.summary = data.summary;
renderUsers();
renderMetrics();
}
async function fetchLicenses() {
const data = await api("/api/licenses");
state.licenses = data;
renderLicenses();
renderMetrics();
}
async function fetchUserDetail(identifier) {
const data = await api(`/api/users/${encodeURIComponent(identifier)}`);
state.selectedUser = data;
fillUserForm(data);
appendConsole("已加载用户", data.userPrincipalName || identifier);
}
function formPayload() {
return {
userPrincipalName: document.getElementById("userPrincipalName").value.trim(),
displayName: document.getElementById("displayName").value.trim(),
givenName: document.getElementById("givenName").value.trim(),
surname: document.getElementById("surname").value.trim(),
department: document.getElementById("department").value.trim(),
jobTitle: document.getElementById("jobTitle").value.trim(),
officeLocation: document.getElementById("officeLocation").value.trim(),
mobilePhone: document.getElementById("mobilePhone").value.trim(),
usageLocation: document.getElementById("usageLocation").value.trim(),
skuPartNumber: document.getElementById("skuPartNumber").value.trim(),
password: document.getElementById("password").value,
accountEnabled: document.getElementById("accountEnabled").checked,
forceChangePasswordNextSignIn: document.getElementById("forceChangePasswordNextSignIn").checked,
};
}
function fillUserForm(user) {
elements.selectedUserId.value = user.id || "";
document.getElementById("userPrincipalName").value = user.userPrincipalName || "";
document.getElementById("displayName").value = user.displayName || "";
document.getElementById("givenName").value = user.givenName || "";
document.getElementById("surname").value = user.surname || "";
document.getElementById("department").value = user.department || "";
document.getElementById("jobTitle").value = user.jobTitle || "";
document.getElementById("officeLocation").value = user.officeLocation || "";
document.getElementById("mobilePhone").value = user.mobilePhone || "";
document.getElementById("usageLocation").value = user.usageLocation || state.bootstrap.defaultUsageLocation || "";
document.getElementById("skuPartNumber").value = (user.licenseLabels && user.licenseLabels[0]) || "";
document.getElementById("password").value = "";
document.getElementById("accountEnabled").checked = Boolean(user.accountEnabled);
document.getElementById("forceChangePasswordNextSignIn").checked = state.bootstrap.forceChangePassword;
}
function clearUserForm() {
state.selectedUser = null;
elements.selectedUserId.value = "";
elements.userForm.reset();
document.getElementById("accountEnabled").checked = true;
document.getElementById("forceChangePasswordNextSignIn").checked = state.bootstrap.forceChangePassword;
document.getElementById("usageLocation").value = state.bootstrap.defaultUsageLocation || "";
}
function toggleUserSelection(identifier, checked) {
if (!identifier) {
return;
}
if (checked) {
state.selectedUsers.add(identifier);
} else {
state.selectedUsers.delete(identifier);
}
updateSelectionUi();
}
function clearSelection() {
state.selectedUsers.clear();
updateSelectionUi();
renderUsers();
}
function toggleCurrentPageSelection(checked) {
state.users.forEach((user) => {
if (!user.userPrincipalName) {
return;
}
if (checked) {
state.selectedUsers.add(user.userPrincipalName);
} else {
state.selectedUsers.delete(user.userPrincipalName);
}
});
renderUsers();
}
async function selectAllMatchingUsers() {
const query = new URLSearchParams({ search: state.search });
const data = await api(`/api/users/selection?${query.toString()}`);
state.selectedUsers = new Set(data.identifiers || []);
renderUsers();
appendConsole("已选中搜索结果", `${state.selectedUsers.size} 个账号`);
}
async function saveUser(event) {
event.preventDefault();
const payload = formPayload();
const identifier = payload.userPrincipalName;
if (!identifier) {
throw new Error("请先填写账号或邮箱。");
}
showSingleActionStatus("保存用户", identifier);
let data;
if (state.selectedUser && state.selectedUser.userPrincipalName) {
data = await api(`/api/users/${encodeURIComponent(state.selectedUser.userPrincipalName)}`, {
method: "PATCH",
body: JSON.stringify(payload),
});
appendConsole("用户已更新", data.user.userPrincipalName || identifier);
} else {
data = await api("/api/users", {
method: "POST",
body: JSON.stringify(payload),
});
appendConsole("用户已创建", data.user.userPrincipalName || identifier);
}
completeSingleActionStatus("保存用户", data.user.userPrincipalName || identifier);
await refreshAll();
if (data.user && data.user.userPrincipalName) {
await fetchUserDetail(data.user.userPrincipalName);
}
}
async function deleteCurrentUser(identifier) {
if (!identifier) {
throw new Error("请先选择一个用户。");
}
const confirmDelete = window.confirm(`确认删除 ${identifier} 吗?此操作不可撤销。`);
if (!confirmDelete) {
return;
}
showSingleActionStatus("删除用户", identifier);
await api(`/api/users/${encodeURIComponent(identifier)}`, { method: "DELETE" });
state.selectedUsers.delete(identifier);
appendConsole("用户已删除", identifier);
completeSingleActionStatus("删除用户", identifier);
clearUserForm();
await refreshAll();
}
async function resetPassword(identifier) {
if (!identifier) {
throw new Error("请先选择一个用户。");
}
const payload = {
password: document.getElementById("password").value,
forceChangePasswordNextSignIn: document.getElementById("forceChangePasswordNextSignIn").checked,
};
showSingleActionStatus("重置密码", identifier);
await api(`/api/users/${encodeURIComponent(identifier)}/reset-password`, {
method: "POST",
body: JSON.stringify(payload),
});
appendConsole("密码已重置", identifier);
completeSingleActionStatus("重置密码", identifier);
}
async function submitBatchTask(action, body, summaryText) {
const task = await api(`/api/users/batch/${action}`, {
method: "POST",
body: JSON.stringify(body),
});
startTask(task, summaryText);
}
async function runSelectedBulk(action) {
const identifiers = selectedIdentifiers();
if (!identifiers.length) {
throw new Error("请先勾选至少一个用户。");
}
if (action === "delete") {
const confirmed = window.confirm(`确认批量删除这 ${identifiers.length} 个账号吗?此操作不可撤销。`);
if (!confirmed) {
return;
}
await submitBatchTask("delete", { identifiers }, `批量删除任务已提交,共 ${identifiers.length} 个账号`);
} else if (action === "enable" || action === "disable") {
const rows = identifiers.map((userPrincipalName) => ({
userPrincipalName,
accountEnabled: action === "enable",
}));
await submitBatchTask("update", { rows }, `${action === "enable" ? "批量启用" : "批量停用"}任务已提交,共 ${identifiers.length} 个账号`);
} else if (action === "reset-password") {
const promptedPassword = window.prompt("输入统一临时密码;留空则使用系统默认密码。", "");
if (promptedPassword === null) {
return;
}
const password = promptedPassword.trim();
const rows = identifiers.map((userPrincipalName) => ({
userPrincipalName,
...(password ? { password } : {}),
forceChangePasswordNextSignIn: true,
}));
await submitBatchTask("reset-password", { rows }, `批量改密任务已提交,共 ${identifiers.length} 个账号`);
} else {
throw new Error("不支持的批量操作。");
}
clearSelection();
}
async function runBatch(action) {
const textareaMap = {
"create": document.getElementById("batch-create-content"),
"update": document.getElementById("batch-update-content"),
"delete": document.getElementById("batch-delete-content"),
"reset-password": document.getElementById("batch-reset-content"),
};
const textarea = textareaMap[action];
const content = textarea.value.trim();
if (!content) {
throw new Error("请先粘贴批量内容或上传文件。");
}
await submitBatchTask(action, { content }, `${formatTaskLabel(action)}任务已提交,等待后台处理`);
}
async function refreshAll() {
updatePlatformStatus();
if (state.bootstrap.authEnabled && !state.authenticated) {
return;
}
if (!state.bootstrap.graphReady) {
return;
}
await Promise.all([fetchUsers(), fetchLicenses()]);
}
function bindEvents() {
elements.loginForm?.addEventListener("submit", async (event) => {
event.preventDefault();
try {
showSingleActionStatus("登录后台", "正在验证管理员账号");
await api("/api/login", {
method: "POST",
body: JSON.stringify({
username: document.getElementById("login-username").value.trim(),
password: document.getElementById("login-password").value,
}),
});
await fetchSession();
await refreshAll();
appendConsole("登录成功", "现在可以开始管理租户账号");
completeSingleActionStatus("登录后台", "登录成功");
} catch (error) {
showFailedActionStatus("登录后台", error.message);
appendConsole("登录失败", error.message);
}
});
elements.logoutBtn?.addEventListener("click", async () => {
await api("/api/logout", { method: "POST" });
state.authenticated = false;
updateAuthView();
appendConsole("已退出后台", "如需继续操作,请重新登录");
});
elements.refreshAllBtn?.addEventListener("click", async () => {
try {
showSingleActionStatus("刷新数据", "正在重新加载用户与许可证");
await refreshAll();
appendConsole("刷新完成", "用户列表和许可证信息已更新");
completeSingleActionStatus("刷新数据", "刷新完成");
} catch (error) {
showFailedActionStatus("刷新数据", error.message);
appendConsole("刷新失败", error.message);
}
});
elements.searchForm?.addEventListener("submit", async (event) => {
event.preventDefault();
state.search = elements.searchInput.value.trim();
state.page = 1;
try {
showSingleActionStatus("搜索用户", state.search || "全部用户");
await fetchUsers();
appendConsole("搜索完成", `${state.total} 条匹配结果`);
completeSingleActionStatus("搜索用户", `${state.total} 条匹配结果`);
} catch (error) {
showFailedActionStatus("搜索用户", error.message);
appendConsole("搜索失败", error.message);
}
});
elements.prevPageBtn?.addEventListener("click", async () => {
if (state.page <= 1) {
return;
}
state.page -= 1;
await fetchUsers();
});
elements.nextPageBtn?.addEventListener("click", async () => {
if (state.page * state.pageSize >= state.total) {
return;
}
state.page += 1;
await fetchUsers();
});
elements.selectPageCheckbox?.addEventListener("change", (event) => {
toggleCurrentPageSelection(event.target.checked);
});
elements.selectAllResultsBtn?.addEventListener("click", async () => {
try {
await selectAllMatchingUsers();
} catch (error) {
appendConsole("全选失败", error.message);
}
});
elements.clearSelectionBtn?.addEventListener("click", () => {
clearSelection();
appendConsole("已清空选择", "当前未勾选任何账号");
});
elements.bulkEnableBtn?.addEventListener("click", async () => {
try {
await runSelectedBulk("enable");
} catch (error) {
appendConsole("批量启用失败", error.message);
}
});
elements.bulkDisableBtn?.addEventListener("click", async () => {
try {
await runSelectedBulk("disable");
} catch (error) {
appendConsole("批量停用失败", error.message);
}
});
elements.bulkResetBtn?.addEventListener("click", async () => {
try {
await runSelectedBulk("reset-password");
} catch (error) {
appendConsole("批量改密失败", error.message);
}
});
elements.bulkDeleteBtn?.addEventListener("click", async () => {
try {
await runSelectedBulk("delete");
} catch (error) {
appendConsole("批量删除失败", error.message);
}
});
elements.usersTableBody?.addEventListener("change", (event) => {
const checkbox = event.target.closest("input[data-select-identifier]");
if (!checkbox) {
return;
}
const identifier = decodeURIComponent(checkbox.dataset.selectIdentifier || "");
toggleUserSelection(identifier, checkbox.checked);
});
elements.usersTableBody?.addEventListener("click", async (event) => {
const button = event.target.closest("button[data-action]");
if (!button) {
return;
}
const action = button.dataset.action;
const identifier = decodeURIComponent(button.dataset.identifier || "");
try {
if (action === "view") {
await fetchUserDetail(identifier);
} else if (action === "delete") {
await deleteCurrentUser(identifier);
} else if (action === "reset") {
await resetPassword(identifier);
}
} catch (error) {
appendConsole("用户操作失败", error.message);
showFailedActionStatus("用户操作失败", error.message);
}
});
elements.userForm?.addEventListener("submit", async (event) => {
try {
await saveUser(event);
} catch (error) {
appendConsole("保存失败", error.message);
showFailedActionStatus("保存用户", error.message);
}
});
elements.newUserBtn?.addEventListener("click", () => clearUserForm());
elements.clearFormBtn?.addEventListener("click", () => clearUserForm());
elements.deleteUserBtn?.addEventListener("click", async () => {
try {
await deleteCurrentUser(state.selectedUser && state.selectedUser.userPrincipalName);
} catch (error) {
appendConsole("删除失败", error.message);
showFailedActionStatus("删除用户", error.message);
}
});
elements.resetPasswordBtn?.addEventListener("click", async () => {
try {
await resetPassword(state.selectedUser && state.selectedUser.userPrincipalName);
} catch (error) {
appendConsole("重置密码失败", error.message);
showFailedActionStatus("重置密码", error.message);
}
});
document.querySelectorAll("[data-batch-action]").forEach((button) => {
button.addEventListener("click", async () => {
try {
await runBatch(button.dataset.batchAction);
} catch (error) {
appendConsole("批量任务提交失败", error.message);
showFailedActionStatus("批量任务提交失败", error.message);
}
});
});
document.querySelectorAll(".file-input").forEach((input) => {
input.addEventListener("change", async (event) => {
const file = event.target.files && event.target.files[0];
if (!file) {
return;
}
const targetId = event.target.dataset.target;
const target = document.getElementById(targetId);
target.value = await file.text();
appendConsole("文件已加载", `${file.name} 已写入输入框`);
});
});
}
async function boot() {
setConsole("等待操作...");
updatePlatformStatus();
bindEvents();
updateSelectionUi();
try {
await fetchSession();
if (!state.bootstrap.authEnabled || state.authenticated) {
await refreshAll();
}
} catch (error) {
appendConsole("初始化失败", error.message);
showFailedActionStatus("初始化失败", error.message);
}
}
boot();