eb32fd5e22
外层仓库管理 webgui 源码、Docker 编排与项目文档;rclone 作为
git submodule 锁定在上游 master HEAD(59c86b01b),不携带任何
我们的改动。
- webgui/: webgui 源码(原本位于 rclone/cmd/webgui/)
- web/: 原生 HTML/CSS/JS 静态前端(Anthropic 设计语言)
- webgui.go: Go 子命令源码,仅当自行构建 rclone 二进制时需要
- rclone-cmd-all-add-webgui-import.patch: 把 webgui 注册进
rclone 的 cmd/all/all.go 的补丁,留作 fork 时使用
- rclone/: submodule → github.com/rclone/rclone,纯净不改动
- Dockerfile.webgui: 基于 nginx:1.27-alpine,从 ./webgui/web/
COPY 静态资源
- docker/nginx.conf: SPA 静态托管 + 反向代理 RC API
(/config/、/operations/、/sync/、/job/ 等) 与文件下载
(/<remote>:<path>) 到 rclone rcd 容器,前端同源访问无 CORS
- docker-compose.yml: rclone (官方镜像 + rcd --rc-no-auth
--rc-serve) + gui (nginx) 双服务编排,config 走 bind mount
持久化
- DESIGN.md / CLAUDE.md / README.md: 文档
- .gitignore / .dockerignore: 排除 rclone.conf 等敏感文件,
Docker 构建上下文只剩 webgui/web/ + nginx 配置(几十 KB)
63 lines
1.7 KiB
JavaScript
63 lines
1.7 KiB
JavaScript
// app.js — entry point. Routes hash changes to view renderers and
|
|
// keeps the top-nav active state in sync.
|
|
|
|
import { onRoute } from "./state.js";
|
|
import { renderRemotes } from "./views/remotes.js";
|
|
import { renderBrowse } from "./views/browser.js";
|
|
import { renderJobs, renderNewJob, stopJobPolling } from "./views/jobs.js";
|
|
import {
|
|
renderConfigureNew,
|
|
renderConfigureEdit,
|
|
} from "./views/configure.js";
|
|
|
|
const views = {
|
|
remotes: renderRemotes,
|
|
browse: renderBrowse,
|
|
jobs: renderJobs,
|
|
"jobs-new": renderNewJob,
|
|
"configure-new": renderConfigureNew,
|
|
"configure-edit": renderConfigureEdit,
|
|
};
|
|
|
|
function setActiveNav(routeName) {
|
|
const links = document.querySelectorAll("#nav-links a");
|
|
for (const a of links) {
|
|
const target = a.dataset.route;
|
|
let isActive = target === routeName;
|
|
// "Configure" is active on both configure-new and configure-edit
|
|
if (target === "configure" && routeName.startsWith("configure-")) {
|
|
isActive = true;
|
|
}
|
|
a.classList.toggle("active", isActive);
|
|
}
|
|
}
|
|
|
|
onRoute(async (route) => {
|
|
setActiveNav(route.name);
|
|
|
|
// Stop jobs polling when leaving the jobs view
|
|
if (route.name !== "jobs") {
|
|
stopJobPolling();
|
|
}
|
|
|
|
const renderer = views[route.name] || renderRemotes;
|
|
try {
|
|
await renderer(route.params);
|
|
} catch (e) {
|
|
console.error("view error", e);
|
|
const app = document.getElementById("app");
|
|
if (app) {
|
|
app.innerHTML = `<div class="empty"><h3>Something went wrong</h3><p>${escapeHtml(e.message)}</p></div>`;
|
|
}
|
|
}
|
|
});
|
|
|
|
function escapeHtml(s) {
|
|
return String(s)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|