Initial UNC file watcher web console
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
package main
|
||||
|
||||
import "html/template"
|
||||
|
||||
var pageTemplate = template.Must(template.New("page").Parse(`<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UNC 文件监听控制台</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f3efe7;
|
||||
--panel: #fffaf2;
|
||||
--panel-strong: #f7efe1;
|
||||
--ink: #1f2520;
|
||||
--muted: #6b716a;
|
||||
--accent: #205c43;
|
||||
--accent-soft: #dcebdd;
|
||||
--warn: #8a3b2f;
|
||||
--warn-soft: #f7ddd8;
|
||||
--line: #d8cfbf;
|
||||
--shadow: rgba(31, 37, 32, 0.08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(32, 92, 67, 0.10), transparent 28%),
|
||||
linear-gradient(180deg, #f8f4ec 0%, var(--bg) 100%);
|
||||
color: var(--ink);
|
||||
}
|
||||
.shell {
|
||||
width: min(1180px, calc(100% - 32px));
|
||||
margin: 24px auto 40px;
|
||||
}
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #214d3a 0%, #2f6a54 100%);
|
||||
color: #f8f5ee;
|
||||
border-radius: 24px;
|
||||
padding: 28px;
|
||||
box-shadow: 0 20px 40px var(--shadow);
|
||||
}
|
||||
.hero h1 {
|
||||
margin: 0 0 10px;
|
||||
font-size: clamp(28px, 5vw, 42px);
|
||||
line-height: 1.05;
|
||||
}
|
||||
.hero p {
|
||||
margin: 0;
|
||||
max-width: 860px;
|
||||
color: rgba(248, 245, 238, 0.85);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.35fr 0.65fr;
|
||||
gap: 18px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 22px;
|
||||
padding: 22px;
|
||||
box-shadow: 0 14px 30px var(--shadow);
|
||||
}
|
||||
.panel h2 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.section {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
padding: 16px;
|
||||
background: #fffdf8;
|
||||
}
|
||||
.section + .section {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.section h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.status-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel-strong);
|
||||
border: 1px solid var(--line);
|
||||
font-weight: 600;
|
||||
}
|
||||
.pill.running {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
border-color: rgba(32, 92, 67, 0.2);
|
||||
}
|
||||
.note {
|
||||
margin: 10px 0 0;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.flash {
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 14px;
|
||||
line-height: 1.6;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.flash.ok {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
border-color: rgba(32, 92, 67, 0.18);
|
||||
}
|
||||
.flash.err {
|
||||
background: var(--warn-soft);
|
||||
color: var(--warn);
|
||||
border-color: rgba(138, 59, 47, 0.18);
|
||||
}
|
||||
form {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
.fields {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.fields .wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
label {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
label span {
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fffdf8;
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
}
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
button {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 12px 20px;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
.primary {
|
||||
background: var(--accent);
|
||||
color: #f7f3ec;
|
||||
}
|
||||
.secondary {
|
||||
background: #e7dfd0;
|
||||
color: #493c2c;
|
||||
}
|
||||
.logs {
|
||||
margin-top: 18px;
|
||||
}
|
||||
.logbox {
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--line);
|
||||
background: #1f2520;
|
||||
color: #dbe5dc;
|
||||
padding: 16px;
|
||||
min-height: 320px;
|
||||
max-height: 560px;
|
||||
overflow: auto;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.empty {
|
||||
color: #9cab9e;
|
||||
}
|
||||
code {
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<section class="hero">
|
||||
<h1>UNC 文件监听控制台</h1>
|
||||
<p>支持两类规则同时运行。压缩包规则专门处理 <code>.tar.gz</code> 并结合 zip / unzip 目录去重;普通文件规则按扩展名列表把文件从 UNC 路径复制或剪切到本机目录,并支持按“同名+大小”或“同名+哈希”去重。</p>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<div class="panel">
|
||||
<h2>监听配置</h2>
|
||||
{{if .SubmitMessage}}<div class="flash ok">{{.SubmitMessage}}</div>{{end}}
|
||||
{{if .SubmitError}}<div class="flash err">{{.SubmitError}}</div>{{end}}
|
||||
{{if .LastError}}<div class="flash err">{{.LastError}}</div>{{end}}
|
||||
<form method="post" action="/start">
|
||||
<div class="section">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="archive_enabled" {{if .Form.ArchiveEnabled}}checked{{end}}>
|
||||
启用压缩包规则
|
||||
</label>
|
||||
<div class="fields">
|
||||
<label class="wide">压缩包 UNC 源路径
|
||||
<span>例如 \\server\share\incoming,仅处理 .tar.gz</span>
|
||||
<input name="archive_source" value="{{.Form.ArchiveSourceDir}}" placeholder="\\server\share\incoming">
|
||||
</label>
|
||||
<label>zip 路径
|
||||
<span>需要复制或剪切到这里</span>
|
||||
<input name="archive_zip" value="{{.Form.ArchiveZipDir}}" placeholder="D:\zip">
|
||||
</label>
|
||||
<label>unzip 路径
|
||||
<span>已有同名解压目录则跳过</span>
|
||||
<input name="archive_unzip" value="{{.Form.ArchiveUnzipDir}}" placeholder="D:\unzip">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="generic_enabled" {{if .Form.GenericEnabled}}checked{{end}}>
|
||||
启用普通文件规则
|
||||
</label>
|
||||
<div class="fields">
|
||||
<label class="wide">普通文件 UNC 源路径
|
||||
<span>例如 \\server\share\others</span>
|
||||
<input name="generic_source" value="{{.Form.GenericSourceDir}}" placeholder="\\server\share\others">
|
||||
</label>
|
||||
<label>本机目标路径
|
||||
<span>只处理到这个目录,不检查 unzip</span>
|
||||
<input name="generic_target" value="{{.Form.GenericTargetDir}}" placeholder="D:\other_files">
|
||||
</label>
|
||||
<label>扩展名列表
|
||||
<span>例如 .csv,.txt,.pdf 或 xlsx,docx</span>
|
||||
<input name="generic_exts" value="{{.Form.GenericExts}}" placeholder=".csv,.txt,.pdf">
|
||||
</label>
|
||||
<label>去重方式
|
||||
<span>目标目录已有同名文件时使用</span>
|
||||
<select name="generic_dedupe">
|
||||
<option value="name_size" {{if eq .Form.GenericDedupe "name_size"}}selected{{end}}>同名且大小相同才跳过</option>
|
||||
<option value="name_hash" {{if eq .Form.GenericDedupe "name_hash"}}selected{{end}}>同名且哈希相同才跳过</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>公共设置</h3>
|
||||
<div class="fields">
|
||||
<label>扫描间隔
|
||||
<span>支持 Go duration,如 15s、1m</span>
|
||||
<input name="interval" value="{{.Form.Interval}}" placeholder="15s">
|
||||
</label>
|
||||
<label>稳定时长
|
||||
<span>最后修改时间至少早于当前多久</span>
|
||||
<input name="stable_for" value="{{.Form.StableFor}}" placeholder="30s">
|
||||
</label>
|
||||
<label>稳定扫描次数
|
||||
<span>连续几次扫描未变化才处理</span>
|
||||
<input name="stable_scans" value="{{.Form.StableScans}}" placeholder="2">
|
||||
</label>
|
||||
<label>处理方式
|
||||
<span>copy 复制,move 剪切</span>
|
||||
<select name="mode">
|
||||
<option value="copy" {{if eq .Form.Mode "copy"}}selected{{end}}>copy</option>
|
||||
<option value="move" {{if eq .Form.Mode "move"}}selected{{end}}>move</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="checkbox" style="margin-top:12px;">
|
||||
<input type="checkbox" name="recursive" {{if .Form.Recursive}}checked{{end}}>
|
||||
递归扫描源目录
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="note">页面配置会自动保存到 <code>{{.SettingsPath}}</code>,下次打开页面会自动带出。</p>
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary" type="submit">启动或重启监听</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>运行状态</h2>
|
||||
<div class="status-line">
|
||||
<div class="pill {{if .Running}}running{{end}}">
|
||||
{{if .Running}}运行中{{else}}未运行{{end}}
|
||||
</div>
|
||||
{{if .StartedAt}}
|
||||
<div class="pill">启动时间 {{.StartedAt}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .LastMessage}}
|
||||
<p class="note">{{.LastMessage}}</p>
|
||||
{{end}}
|
||||
<p class="note">如果 UNC 目录中的文件由后台服务持续写入,建议把“稳定时长”和“稳定扫描次数”设得更保守一些,例如 <code>stable_for=1m</code>、<code>stable_scans=3</code>。</p>
|
||||
<p class="note">普通文件规则里如果目标目录出现同名但不同大小,或者同名但哈希不同,程序会记录为冲突并跳过,不会覆盖已有文件。</p>
|
||||
<form method="post" action="/stop">
|
||||
<div class="actions">
|
||||
<button class="secondary" type="submit">停止监听</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel logs">
|
||||
<h2>最近日志</h2>
|
||||
<div class="logbox">{{if .Logs}}{{range .Logs}}{{.}}
|
||||
{{end}}{{else}}<span class="empty">暂无日志。启动监听后,这里会显示文件发现、跳过、复制、剪切和错误信息。</span>{{end}}</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>`))
|
||||
Reference in New Issue
Block a user