Files
rclone_gui/webgui/web/assets/js/views/remotes.js
T
ci eb32fd5e22 init: 初始化 rclone-webgui 项目结构
外层仓库管理 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)
2026-06-19 12:42:35 +08:00

106 lines
3.2 KiB
JavaScript
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.
// views/remotes.js — connector-tile grid of configured remotes with CRUD.
import { post } from "../rc.js";
import { getState, setState, toast } from "../state.js";
export async function renderRemotes() {
const app = document.getElementById("app");
if (!app) return;
app.innerHTML = `
<div class="section-head">
<div>
<h2>Remotes</h2>
<p class="subtitle">Configured cloud storage providers. Click a tile to browse, hover for edit/delete.</p>
</div>
<a class="btn btn-primary btn-sm" href="#/configure/new">New remote</a>
</div>
<div id="remote-grid" class="connector-grid">
<p class="empty" style="grid-column:1/-1">Loading remotes…</p>
</div>
`;
const grid = document.getElementById("remote-grid");
try {
const [listRes, dumpRes] = await Promise.all([
post("config/listremotes"),
post("config/dump"),
]);
const names = (listRes && listRes.remotes) || [];
const dump = dumpRes || {};
const remotes = names.map((name) => ({
name,
type: (dump[name] && dump[name].type) || "unknown",
}));
setState({ remotes });
if (remotes.length === 0) {
grid.innerHTML = `
<div class="empty" style="grid-column:1/-1">
<h3>No remotes configured</h3>
<p>Click <strong>New remote</strong> above to add one from this GUI.</p>
</div>
`;
return;
}
grid.innerHTML = remotes
.map(
(r) => `
<a class="connector-tile tile-with-actions" href="#/browse/${encodeURIComponent(r.name)}">
<span class="tile-name">${escapeHtml(r.name)}</span>
<span class="badge">${escapeHtml(r.type)}</span>
<div class="tile-actions">
<button type="button" title="Edit" data-edit="${escapeHtml(r.name)}">✎</button>
<button type="button" title="Delete" class="danger" data-delete="${escapeHtml(r.name)}">🗑</button>
</div>
</a>
`,
)
.join("");
// Wire action buttons
grid.querySelectorAll("[data-edit]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
location.hash = `#/configure/edit/${encodeURIComponent(btn.dataset.edit)}`;
});
});
grid.querySelectorAll("[data-delete]").forEach((btn) => {
btn.addEventListener("click", async (e) => {
e.preventDefault();
e.stopPropagation();
const name = btn.dataset.delete;
if (!confirm(`Delete remote ${name}? This cannot be undone.`)) return;
try {
await post("config/delete", { name });
toast(`Deleted ${name}`, "success");
await renderRemotes();
} catch (err) {
toast(`Delete failed: ${err.message}`, "error");
}
});
});
} catch (e) {
grid.innerHTML = `
<div class="empty" style="grid-column:1/-1">
<h3>Couldnt reach rclone</h3>
<p>${escapeHtml(e.message)}</p>
</div>
`;
toast(`Failed to load remotes: ${e.message}`, "error");
}
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}