- 确认端点单元测试:有效/复用/过期/串用/乱写 token 各测一遍,token 即凭证不经 Auth - WebUI 重建:对齐新 API 的操作类型(含 delete_user)、用户生命周期状态视图、 概览含离职待确认列表与被跳过告警、「立即快照」与「立即物化」触发按钮、 部门分组展示 groupId;重编译 app.css/app.js 并提交产物 - 界面文案改指 LLDAP(不再提 OpenLDAP),确认弹窗说明离职才物理删除
250 lines
11 KiB
HTML
250 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN" data-theme="light">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>idsync · 飞书通讯录同步</title>
|
||
<link rel="stylesheet" href="/static/app.css" />
|
||
</head>
|
||
<body class="bg-base-200 min-h-screen">
|
||
|
||
<!-- 未登录:登录卡片 -->
|
||
<div id="loginView" class="hidden items-center justify-center min-h-screen p-4">
|
||
<div class="card w-full max-w-sm bg-base-100 shadow-lg">
|
||
<div class="card-body">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<span class="inline-block size-3 rounded-full bg-primary"></span>
|
||
<h1 class="card-title text-lg m-0">idsync</h1>
|
||
</div>
|
||
<p class="text-sm text-base-content/60 mb-4">飞书通讯录 → LLDAP 同步</p>
|
||
|
||
<div id="loginError" class="alert alert-error text-sm mb-3 hidden"></div>
|
||
|
||
<label class="label" for="username"><span class="label-text">用户名</span></label>
|
||
<input id="username" type="text" class="input w-full mb-3" autocomplete="username" />
|
||
|
||
<label class="label" for="password"><span class="label-text">密码</span></label>
|
||
<input id="password" type="password" class="input w-full mb-5" autocomplete="current-password" />
|
||
|
||
<button id="loginBtn" class="btn btn-primary w-full">登录</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 已登录:主界面 -->
|
||
<div id="appView" class="hidden">
|
||
<header class="navbar bg-base-100 shadow-sm px-4 sticky top-0 z-20">
|
||
<div class="flex-1 flex items-center gap-2">
|
||
<span class="inline-block size-3 rounded-full bg-primary"></span>
|
||
<span class="text-lg font-semibold">idsync</span>
|
||
<span class="hidden sm:inline text-sm text-base-content/50">飞书通讯录 → LLDAP</span>
|
||
</div>
|
||
<div class="flex-none flex items-center gap-2">
|
||
<button id="themeToggle" class="btn btn-sm btn-ghost" title="切换明暗主题">主题</button>
|
||
<button id="logoutBtn" class="btn btn-sm btn-outline">退出</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="max-w-7xl mx-auto p-4">
|
||
<!-- 页签 -->
|
||
<div class="tabs tabs-border mb-4" role="tablist">
|
||
<button class="tab tab-active" data-view="overview" role="tab">概览</button>
|
||
<button class="tab" data-view="users" role="tab">用户</button>
|
||
<button class="tab" data-view="groups" role="tab">部门分组</button>
|
||
<button class="tab" data-view="plan" role="tab">变更预览</button>
|
||
<button class="tab" data-view="runs" role="tab">同步历史</button>
|
||
</div>
|
||
|
||
<div id="globalError" class="alert alert-error text-sm mb-4 hidden"></div>
|
||
|
||
<!-- 概览 -->
|
||
<section id="view-overview" class="view">
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<span class="text-sm text-base-content/60">在职账号</span>
|
||
<span id="statActive" class="text-3xl font-semibold">–</span>
|
||
</div>
|
||
</div>
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<span class="text-sm text-base-content/60">在飞书可见/待确认离职</span>
|
||
<span id="statPending" class="text-3xl font-semibold">–</span>
|
||
</div>
|
||
</div>
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<span class="text-sm text-base-content/60">部门分组</span>
|
||
<span id="statGroups" class="text-3xl font-semibold">–</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<h2 class="card-title text-base">服务状态</h2>
|
||
<div id="connStatus" class="space-y-2 text-sm"></div>
|
||
<div id="skippedStatusWrap" class="hidden mt-2 text-sm rounded-lg bg-warning/10 px-3 py-2">
|
||
有 <span id="skippedStatus" class="font-semibold"></span> 名用户因缺少企业邮箱被跳过,
|
||
请到用户页查看并处理,避免他们一直没被同步。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<h2 class="card-title text-base">上次物化</h2>
|
||
<div id="lastRunInfo" class="text-sm text-base-content/70">暂无记录</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 离职待确认 -->
|
||
<div class="card bg-base-100 shadow-sm mt-4">
|
||
<div class="card-body">
|
||
<div class="flex items-center justify-between mb-1">
|
||
<h2 class="card-title text-base">离职待确认</h2>
|
||
<button id="refreshPending" class="btn btn-sm btn-outline">刷新</button>
|
||
</div>
|
||
<p class="text-sm text-base-content/60 mb-2">
|
||
这些用户已在飞书确认离职并收到确认卡片,等待人工点击确认后才会被删除。
|
||
</p>
|
||
<ul id="pendingRows" class="divide-y divide-base-200"></ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 用户 -->
|
||
<section id="view-users" class="view hidden">
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<div class="flex flex-wrap items-center justify-between gap-3 mb-3">
|
||
<h2 class="card-title text-base">受管用户</h2>
|
||
<div class="flex items-center gap-2">
|
||
<input id="userFilter" type="search" class="input input-sm w-56" placeholder="按 uid / 姓名 / 邮箱过滤" />
|
||
<button id="reloadUsers" class="btn btn-sm btn-outline">刷新</button>
|
||
</div>
|
||
</div>
|
||
<div class="idsync-scroll">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr>
|
||
<th>uid</th><th>姓名</th><th>企业邮箱</th><th>状态</th>
|
||
<th>归属部门数</th><th>最后同步</th><th>一致性</th><th>DN</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="userRows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="userEmpty" class="hidden text-sm text-base-content/50 py-4 text-center">暂无受管用户,请先执行一次同步。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 部门分组 -->
|
||
<section id="view-groups" class="view hidden">
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<h2 class="card-title text-base mb-3">受管部门分组</h2>
|
||
<div class="idsync-scroll">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr><th>部门</th><th>飞书部门 ID</th><th>父部门</th><th>DN</th></tr>
|
||
</thead>
|
||
<tbody id="groupRows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="groupEmpty" class="hidden text-sm text-base-content/50 py-4 text-center">暂无受管分组。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 变更预览 -->
|
||
<section id="view-plan" class="view hidden">
|
||
<div class="card bg-base-100 shadow-sm mb-4">
|
||
<div class="card-body">
|
||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||
<div>
|
||
<h2 class="card-title text-base">变更预览(dry-run)</h2>
|
||
<p class="text-sm text-base-content/60 mt-1">
|
||
预览与实际执行使用同一份计划代码,因此这里显示的就是将要发生的变更。
|
||
</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button id="snapshotBtn" class="btn btn-sm btn-neutral">立即快照</button>
|
||
<button id="previewBtn" class="btn btn-sm btn-outline">重新计算</button>
|
||
<button id="applyBtn" class="btn btn-sm btn-primary">应用变更</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="planWarnings" class="hidden mb-4"></div>
|
||
|
||
<div id="planSummary" class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-3 mb-4"></div>
|
||
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-base mb-2">操作明细</h3>
|
||
<div class="idsync-scroll">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr><th class="w-40">类型</th><th class="w-56">对象</th><th>说明</th></tr>
|
||
</thead>
|
||
<tbody id="planRows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="planEmpty" class="hidden text-sm text-base-content/50 py-4 text-center">
|
||
没有需要变更的内容,两侧状态已一致。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 同步历史 -->
|
||
<section id="view-runs" class="view hidden">
|
||
<div class="card bg-base-100 shadow-sm">
|
||
<div class="card-body">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h2 class="card-title text-base">同步历史</h2>
|
||
<button id="reloadRuns" class="btn btn-sm btn-outline">刷新</button>
|
||
</div>
|
||
<div class="idsync-scroll">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr>
|
||
<th>开始时间</th><th>模式</th><th>触发</th><th>结果</th>
|
||
<th>新建</th><th>更新</th><th>迁移</th><th>加组</th><th>移组</th><th>失败</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="runRows"></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="runEmpty" class="hidden text-sm text-base-content/50 py-4 text-center">暂无同步记录。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- 应用变更的确认对话框 -->
|
||
<dialog id="applyModal" class="modal">
|
||
<div class="modal-box">
|
||
<h3 class="text-lg font-semibold mb-2">确认应用变更</h3>
|
||
<p class="text-sm text-base-content/70 mb-4">
|
||
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
|
||
仅标记「已确认离职」的用户会被物理删除。
|
||
</p>
|
||
<div id="applyModalBody" class="text-sm mb-4"></div>
|
||
<div class="modal-action">
|
||
<button id="applyCancel" class="btn btn-ghost">取消</button>
|
||
<button id="applyConfirm" class="btn btn-primary">确认执行</button>
|
||
</div>
|
||
</div>
|
||
</dialog>
|
||
|
||
<script src="/static/flyonui.js"></script>
|
||
<script src="/static/app.js"></script>
|
||
</body>
|
||
</html>
|