Files
idsync/internal/webui/dist/index.html
T
caowangrenbo 1f11e615a9 fix: WebUI 应用变更确认框改用 FlyonUI overlay 组件
原实现混用了 DaisyUI 的写法(<dialog class="modal"> + .modal-box),
而项目实际引入的是 FlyonUI:其 modal 由 HSOverlay 驱动,显示态靠
overlay-open 变体(&.open)生成,且该变体不会随 @plugin 自动注册
(Tailwind v4 只加载包内同名 flyonui.css,variants.css 被忽略)。

后果是 showModal() 只产生原生遮罩(页面变暗+其余按钮被锁),
对话框本身始终 opacity:0 不可见,也未发出任何请求。

本次改动:
- modal 结构迁移为官方 overlay 结构,开合改用 HSOverlay.open/close,
  取消/关闭按钮走 data-overlay 声明式关闭;
- input.css 注册 overlay-open 变体(带注释说明不可删);
- 顺带修正其余 DaisyUI 残留:tabs-border→tabs-bordered、
  btn-ghost/badge-ghost/badge-neutral→btn-soft/badge-soft。
2026-09-30 00:41:03 +08:00

262 lines
12 KiB
HTML
Raw 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.
<!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-soft" 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-bordered 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>
<!-- 应用变更的确认对话框。
必须用 FlyonUI 的 overlay 结构:容器是 .overlay.modal 的 <div>(HSOverlay 靠切换
open/hidden class 显隐,不会调用原生 dialog.showModal),显示态由 overlay-open 变体
生成——所以 overlay-open:opacity-100 这个类不能删,否则对话框又会变回不可见。 -->
<div id="applyModal" class="overlay modal modal-middle overlay-open:opacity-100 hidden overlay-open:duration-300"
role="dialog" tabindex="-1" aria-labelledby="applyModalTitle">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 id="applyModalTitle" class="modal-title">确认应用变更</h3>
<button type="button" class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
aria-label="关闭" data-overlay="#applyModal">✕</button>
</div>
<div class="modal-body">
<p class="text-sm text-base-content/70 mb-4">
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
仅标记「已确认离职」的用户会被物理删除。
</p>
<div id="applyModalBody" class="text-sm"></div>
</div>
<div class="modal-footer">
<button id="applyCancel" type="button" class="btn btn-soft" data-overlay="#applyModal">取消</button>
<button id="applyConfirm" type="button" class="btn btn-primary">确认执行</button>
</div>
</div>
</div>
</div>
<script src="/static/flyonui.js"></script>
<script src="/static/app.js"></script>
</body>
</html>