565 lines
17 KiB
Markdown
565 lines
17 KiB
Markdown
# Web Page Split And Root Source Cleanup
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Restructure the `web/` directory into `core/` + `feeder/` + `ops/` subdirectories, delete the obsolete root `src/` files, and update README to reflect the new workspace layout.
|
||
|
||
**Architecture:** Web pages split into a shared `web/core/` (platform HTML partials and CSS) and per-app directories (`web/feeder/`, `web/ops/`). Each app's Axum router uses `ServeDir` with fallback: try app-specific dir first, then core. This means no URL changes in HTML/JS — the fallback chain resolves transparently. The root `src/` contains stale copies of files already migrated to crates and must be removed.
|
||
|
||
**Tech Stack:** Rust (Axum, tower-http ServeDir), HTML/CSS/JS (vanilla, ES modules), Cargo workspace
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
### Web core (shared platform pages)
|
||
|
||
- Move: `web/styles.css` → `web/core/styles.css`
|
||
- Move: `web/html/source-panel.html` → `web/core/html/source-panel.html`
|
||
- Move: `web/html/points-panel.html` → `web/core/html/points-panel.html`
|
||
- Move: `web/html/equipment-panel.html` → `web/core/html/equipment-panel.html`
|
||
- Move: `web/html/chart-panel.html` → `web/core/html/chart-panel.html`
|
||
- Move: `web/html/log-stream-panel.html` → `web/core/html/log-stream-panel.html`
|
||
- Move: `web/html/logs-panel.html` → `web/core/html/logs-panel.html`
|
||
- Move: `web/html/api-doc-drawer.html` → `web/core/html/api-doc-drawer.html`
|
||
- Create: `web/core/html/modals.html` (core modals only — equipment, source, point, binding; unit modal removed)
|
||
|
||
### Web feeder (feeder-specific pages + all JS)
|
||
|
||
- Move: `web/index.html` → `web/feeder/index.html` (add unit-modal partial reference)
|
||
- Move: `web/html/topbar.html` → `web/feeder/html/topbar.html`
|
||
- Move: `web/html/ops-panel.html` → `web/feeder/html/ops-panel.html`
|
||
- Create: `web/feeder/html/unit-modal.html` (extracted from old modals.html)
|
||
- Move: `web/js/*.js` → `web/feeder/js/*.js` (all 15 JS files stay together as interconnected module graph)
|
||
|
||
### Web ops (operation-system pages)
|
||
|
||
- Move: `crates/app_operation_system/web/index.html` → `web/ops/index.html` (updated content)
|
||
- Create: `web/ops/html/topbar.html`
|
||
- Create: `web/ops/js/index.js`
|
||
- Create: `web/ops/js/app.js`
|
||
|
||
### Rust router changes
|
||
|
||
- Modify: `crates/app_feeder_distributor/src/router.rs` (update ServeDir to use fallback)
|
||
- Modify: `crates/app_operation_system/src/router.rs` (update ServeDir to use fallback)
|
||
|
||
### Root src cleanup
|
||
|
||
- Delete: all 19 files under `src/` (stale duplicates of files in crates)
|
||
|
||
### Documentation
|
||
|
||
- Modify: `README.md`
|
||
|
||
---
|
||
|
||
## Task 1: Split modals.html And Create Unit Modal Partial
|
||
|
||
**Files:**
|
||
- Create: `web/core/html/modals.html`
|
||
- Create: `web/feeder/html/unit-modal.html`
|
||
|
||
- [ ] **Step 1: Create core modals (without unit modal)**
|
||
|
||
Extract everything except the unit modal div from `web/html/modals.html` into a new file:
|
||
|
||
`web/core/html/modals.html`:
|
||
```html
|
||
<div class="modal hidden" id="equipmentModal">
|
||
<!-- keep entire equipment modal as-is from current modals.html lines 53-87 -->
|
||
</div>
|
||
|
||
<div class="modal hidden" id="pointModal">
|
||
<!-- keep entire point modal as-is from current modals.html lines 89-107 -->
|
||
</div>
|
||
|
||
<div class="modal hidden" id="sourceModal">
|
||
<!-- keep entire source modal as-is from current modals.html lines 109-135 -->
|
||
</div>
|
||
|
||
<div class="modal hidden" id="pointBindingModal">
|
||
<!-- keep entire binding modal as-is from current modals.html lines 137-163 -->
|
||
</div>
|
||
|
||
<div class="modal hidden" id="batchBindingModal">
|
||
<!-- keep entire batch binding modal as-is from current modals.html lines 165-187 -->
|
||
</div>
|
||
```
|
||
|
||
- [ ] **Step 2: Create feeder unit modal partial**
|
||
|
||
Extract the unit modal into its own file:
|
||
|
||
`web/feeder/html/unit-modal.html`:
|
||
```html
|
||
<div class="modal hidden" id="unitModal">
|
||
<!-- keep entire unit modal as-is from current modals.html lines 1-51 -->
|
||
</div>
|
||
```
|
||
|
||
- [ ] **Step 3: Verify both files contain all original modal content**
|
||
|
||
Check that the combined line count of the two new files matches the original `web/html/modals.html` (188 lines total, minus blank lines between sections).
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add web/core/html/modals.html web/feeder/html/unit-modal.html
|
||
git commit -m "refactor(web): split modals into core and feeder unit-modal"
|
||
```
|
||
|
||
## Task 2: Move Core HTML And CSS Into web/core
|
||
|
||
**Files:**
|
||
- Move: `web/styles.css` → `web/core/styles.css`
|
||
- Move: `web/html/source-panel.html` → `web/core/html/source-panel.html`
|
||
- Move: `web/html/points-panel.html` → `web/core/html/points-panel.html`
|
||
- Move: `web/html/equipment-panel.html` → `web/core/html/equipment-panel.html`
|
||
- Move: `web/html/chart-panel.html` → `web/core/html/chart-panel.html`
|
||
- Move: `web/html/log-stream-panel.html` → `web/core/html/log-stream-panel.html`
|
||
- Move: `web/html/logs-panel.html` → `web/core/html/logs-panel.html`
|
||
- Move: `web/html/api-doc-drawer.html` → `web/core/html/api-doc-drawer.html`
|
||
|
||
- [ ] **Step 1: Create core directories and move files**
|
||
|
||
```bash
|
||
mkdir -p web/core/html
|
||
git mv web/styles.css web/core/styles.css
|
||
git mv web/html/source-panel.html web/core/html/source-panel.html
|
||
git mv web/html/points-panel.html web/core/html/points-panel.html
|
||
git mv web/html/equipment-panel.html web/core/html/equipment-panel.html
|
||
git mv web/html/chart-panel.html web/core/html/chart-panel.html
|
||
git mv web/html/log-stream-panel.html web/core/html/log-stream-panel.html
|
||
git mv web/html/logs-panel.html web/core/html/logs-panel.html
|
||
git mv web/html/api-doc-drawer.html web/core/html/api-doc-drawer.html
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add web/core
|
||
git commit -m "refactor(web): move shared HTML partials and CSS into web/core"
|
||
```
|
||
|
||
## Task 3: Move Feeder-Specific HTML And All JS Into web/feeder
|
||
|
||
**Files:**
|
||
- Move: `web/index.html` → `web/feeder/index.html`
|
||
- Move: `web/html/topbar.html` → `web/feeder/html/topbar.html`
|
||
- Move: `web/html/ops-panel.html` → `web/feeder/html/ops-panel.html`
|
||
- Move: `web/js/*.js` → `web/feeder/js/*.js`
|
||
- Delete: `web/html/modals.html` (replaced by split files in Task 1)
|
||
|
||
- [ ] **Step 1: Create feeder directories and move files**
|
||
|
||
```bash
|
||
mkdir -p web/feeder/html web/feeder/js
|
||
git mv web/html/topbar.html web/feeder/html/topbar.html
|
||
git mv web/html/ops-panel.html web/feeder/html/ops-panel.html
|
||
git mv web/js/api.js web/feeder/js/api.js
|
||
git mv web/js/app.js web/feeder/js/app.js
|
||
git mv web/js/chart.js web/feeder/js/chart.js
|
||
git mv web/js/docs.js web/feeder/js/docs.js
|
||
git mv web/js/dom.js web/feeder/js/dom.js
|
||
git mv web/js/equipment.js web/feeder/js/equipment.js
|
||
git mv web/js/events.js web/feeder/js/events.js
|
||
git mv web/js/index.js web/feeder/js/index.js
|
||
git mv web/js/logs.js web/feeder/js/logs.js
|
||
git mv web/js/ops.js web/feeder/js/ops.js
|
||
git mv web/js/points.js web/feeder/js/points.js
|
||
git mv web/js/roles.js web/feeder/js/roles.js
|
||
git mv web/js/sources.js web/feeder/js/sources.js
|
||
git mv web/js/state.js web/feeder/js/state.js
|
||
git mv web/js/units.js web/feeder/js/units.js
|
||
```
|
||
|
||
- [ ] **Step 2: Move index.html and delete old modals**
|
||
|
||
```bash
|
||
git mv web/index.html web/feeder/index.html
|
||
git rm web/html/modals.html
|
||
```
|
||
|
||
- [ ] **Step 3: Update feeder index.html to add unit-modal partial**
|
||
|
||
In `web/feeder/index.html`, change the modals partial line and add a unit-modal partial:
|
||
|
||
Before:
|
||
```html
|
||
<div data-partial="/ui/html/modals.html"></div>
|
||
```
|
||
|
||
After:
|
||
```html
|
||
<div data-partial="/ui/html/modals.html"></div>
|
||
<div data-partial="/ui/html/unit-modal.html"></div>
|
||
```
|
||
|
||
- [ ] **Step 4: Verify no files remain in old web/html and web/js directories**
|
||
|
||
```bash
|
||
ls web/html/ 2>/dev/null && echo "ERROR: web/html still has files" || echo "OK: web/html is clean"
|
||
ls web/js/ 2>/dev/null && echo "ERROR: web/js still has files" || echo "OK: web/js is clean"
|
||
```
|
||
|
||
Expected: both directories are empty or deleted.
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add web/feeder
|
||
git commit -m "refactor(web): move feeder HTML, JS, and index into web/feeder"
|
||
```
|
||
|
||
## Task 4: Update Feeder Router To Use Fallback ServeDir
|
||
|
||
**Files:**
|
||
- Modify: `crates/app_feeder_distributor/src/router.rs`
|
||
|
||
- [ ] **Step 1: Update the static file serving to use fallback chain**
|
||
|
||
In `crates/app_feeder_distributor/src/router.rs`, replace the current `/ui` nest:
|
||
|
||
Before:
|
||
```rust
|
||
.nest(
|
||
"/ui",
|
||
Router::new()
|
||
.fallback_service(ServeDir::new("web").append_index_html_on_directories(true))
|
||
.layer(axum::middleware::from_fn(no_cache)),
|
||
)
|
||
```
|
||
|
||
After:
|
||
```rust
|
||
.nest(
|
||
"/ui",
|
||
Router::new()
|
||
.fallback_service(
|
||
ServeDir::new("web/feeder")
|
||
.append_index_html_on_directories(true)
|
||
.fallback(ServeDir::new("web/core")),
|
||
)
|
||
.layer(axum::middleware::from_fn(no_cache)),
|
||
)
|
||
```
|
||
|
||
- [ ] **Step 2: Verify feeder crate compiles**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cargo check -p app_feeder_distributor
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add crates/app_feeder_distributor/src/router.rs
|
||
git commit -m "refactor(feeder): update static file serving for split web dirs"
|
||
```
|
||
|
||
## Task 5: Create Operation-System Web Pages And Update Router
|
||
|
||
**Files:**
|
||
- Create: `web/ops/index.html`
|
||
- Create: `web/ops/html/topbar.html`
|
||
- Create: `web/ops/js/index.js`
|
||
- Create: `web/ops/js/app.js`
|
||
- Modify: `crates/app_operation_system/src/router.rs`
|
||
- Delete: `crates/app_operation_system/web/index.html`
|
||
|
||
- [ ] **Step 1: Create ops web scaffold**
|
||
|
||
`web/ops/index.html`:
|
||
```html
|
||
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>运转系统</title>
|
||
<link rel="stylesheet" href="/ui/styles.css" />
|
||
</head>
|
||
<body>
|
||
<div data-partial="/ui/html/topbar.html"></div>
|
||
|
||
<main>
|
||
<div class="muted" style="padding:2rem;text-align:center">运转系统页面开发中</div>
|
||
</main>
|
||
|
||
<script type="module" src="/ui/js/index.js"></script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
`web/ops/html/topbar.html`:
|
||
```html
|
||
<header class="topbar">
|
||
<div class="title">运转系统</div>
|
||
<div class="topbar-actions">
|
||
<div class="status" id="statusText">
|
||
<span class="ws-dot" id="wsDot"></span>
|
||
<span id="wsLabel">连接中…</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
```
|
||
|
||
`web/ops/js/index.js`:
|
||
```javascript
|
||
async function loadPartial(slot) {
|
||
const response = await fetch(slot.dataset.partial);
|
||
if (!response.ok) {
|
||
throw new Error(`Failed to load partial: ${slot.dataset.partial}`);
|
||
}
|
||
|
||
const html = await response.text();
|
||
slot.insertAdjacentHTML("beforebegin", html);
|
||
slot.remove();
|
||
}
|
||
|
||
async function bootstrapPage() {
|
||
const slots = Array.from(document.querySelectorAll("[data-partial]"));
|
||
await Promise.all(slots.map((slot) => loadPartial(slot)));
|
||
await import("./app.js");
|
||
}
|
||
|
||
bootstrapPage().catch((error) => {
|
||
document.body.innerHTML = `<pre>${error.message || String(error)}</pre>`;
|
||
});
|
||
```
|
||
|
||
`web/ops/js/app.js`:
|
||
```javascript
|
||
function bootstrap() {
|
||
console.log("Operation system app initialized");
|
||
}
|
||
|
||
bootstrap();
|
||
```
|
||
|
||
- [ ] **Step 2: Update ops router to use split web dirs**
|
||
|
||
Replace `crates/app_operation_system/src/router.rs`:
|
||
|
||
```rust
|
||
use axum::{extract::State, routing::get, Router};
|
||
use tower_http::services::ServeDir;
|
||
|
||
use crate::app::AppState;
|
||
|
||
async fn no_cache(req: axum::extract::Request, next: axum::middleware::Next) -> axum::response::Response {
|
||
let mut response = next.run(req).await;
|
||
response.headers_mut().insert(
|
||
axum::http::header::CACHE_CONTROL,
|
||
axum::http::HeaderValue::from_static("no-store"),
|
||
);
|
||
response
|
||
}
|
||
|
||
pub fn build_router(state: AppState) -> Router {
|
||
Router::new()
|
||
.route("/api/health", get(health_check))
|
||
.nest(
|
||
"/ui",
|
||
Router::new()
|
||
.fallback_service(
|
||
ServeDir::new("web/ops")
|
||
.append_index_html_on_directories(true)
|
||
.fallback(ServeDir::new("web/core")),
|
||
)
|
||
.layer(axum::middleware::from_fn(no_cache)),
|
||
)
|
||
.with_state(state)
|
||
}
|
||
|
||
async fn health_check(State(state): State<AppState>) -> String {
|
||
format!("{}:ok", state.app_name)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Delete old ops web placeholder**
|
||
|
||
```bash
|
||
git rm crates/app_operation_system/web/index.html
|
||
rmdir crates/app_operation_system/web 2>/dev/null || true
|
||
```
|
||
|
||
- [ ] **Step 4: Verify ops crate compiles**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cargo check -p app_operation_system
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 5: Update ops router smoke test if needed**
|
||
|
||
Check `crates/app_operation_system/tests/router_smoke.rs` — if it references the old `WEB_ROOT` constant, update accordingly.
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add web/ops crates/app_operation_system
|
||
git commit -m "refactor(ops): add ops web scaffold and update router for split dirs"
|
||
```
|
||
|
||
## Task 6: Delete Obsolete Root src/ Files
|
||
|
||
**Files:**
|
||
- Delete: all 19 files under `src/`
|
||
|
||
- [ ] **Step 1: Verify all root src files are duplicates of crate files**
|
||
|
||
Run quick checks:
|
||
|
||
```bash
|
||
diff src/config.rs crates/app_feeder_distributor/src/config.rs
|
||
diff src/handler.rs crates/app_feeder_distributor/src/handler.rs
|
||
diff src/middleware.rs crates/app_feeder_distributor/src/middleware.rs
|
||
```
|
||
|
||
All should show no functional differences (only BOM or whitespace).
|
||
|
||
- [ ] **Step 2: Remove all root src files from git**
|
||
|
||
```bash
|
||
git rm -r src/
|
||
```
|
||
|
||
- [ ] **Step 3: Verify workspace still builds**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cargo check --workspace
|
||
```
|
||
|
||
Expected: PASS (root src/ is not a workspace member, removing it changes nothing for the build)
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git commit -m "chore: remove obsolete root src/ (migrated to crates)"
|
||
```
|
||
|
||
## Task 7: Update README
|
||
|
||
**Files:**
|
||
- Modify: `README.md`
|
||
|
||
- [ ] **Step 1: Update the README to reflect the workspace structure**
|
||
|
||
Replace the outdated "后端结构" and add build instructions. Key sections to update:
|
||
|
||
- Remove references to `src/main.rs`, `src/handler`, `src/service`
|
||
- Add workspace structure overview:
|
||
|
||
```markdown
|
||
## 项目结构
|
||
|
||
```text
|
||
plc_control/
|
||
Cargo.toml # Workspace root
|
||
crates/
|
||
plc_platform_core/ # 共享平台核心库
|
||
app_feeder_distributor/ # 投煤器布料机专用版
|
||
app_operation_system/ # 运转系统专用版
|
||
web/
|
||
core/ # 共享 HTML/CSS(点位、设备、数据源等)
|
||
feeder/ # 投煤器布料机页面 + JS
|
||
ops/ # 运转系统页面 + JS
|
||
```
|
||
|
||
## 构建
|
||
|
||
```powershell
|
||
# 投煤器布料机
|
||
cargo build -p app_feeder_distributor --release
|
||
|
||
# 运转系统
|
||
cargo build -p app_operation_system --release
|
||
```
|
||
|
||
## 部署
|
||
|
||
将编译产物和 `web/` 目录放在同一级目录下:
|
||
|
||
```text
|
||
deploy/
|
||
app_feeder_distributor.exe
|
||
web/
|
||
core/
|
||
feeder/
|
||
```
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add README.md
|
||
git commit -m "docs: update README for workspace and web split layout"
|
||
```
|
||
|
||
## Task 8: Final Verification
|
||
|
||
- [ ] **Step 1: Run all tests**
|
||
|
||
```bash
|
||
cargo test --workspace
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 2: Run release builds**
|
||
|
||
```bash
|
||
cargo build -p app_feeder_distributor --release
|
||
cargo build -p app_operation_system --release
|
||
```
|
||
|
||
Expected: both produce binaries successfully.
|
||
|
||
- [ ] **Step 3: Verify web file layout**
|
||
|
||
```bash
|
||
find web -type f | sort
|
||
```
|
||
|
||
Expected: files organized under `web/core/`, `web/feeder/`, `web/ops/` only. No files remaining directly under `web/html/` or `web/js/`.
|
||
|
||
## Self-Review
|
||
|
||
### Spec coverage
|
||
|
||
- Web split into core + per-app directories: Tasks 1–5
|
||
- Fallback ServeDir for transparent URL resolution: Tasks 4–5
|
||
- Root src cleanup: Task 6
|
||
- README update: Task 7
|
||
- Build verification: Task 8
|
||
|
||
### Key design decision: ServeDir fallback
|
||
|
||
Using `ServeDir::new("web/feeder").fallback(ServeDir::new("web/core"))` means:
|
||
- No URL changes needed in any HTML partial references or JS imports
|
||
- App-specific files override core files of the same name (app takes priority)
|
||
- Browser requests are resolved transparently through the chain
|
||
|
||
### Spec deviation: web directory location
|
||
|
||
The original design spec §8.4 suggested per-app web directories inside each crate (`app_feeder_distributor/web`, `app_operation_system/web`). This plan deliberately places web files at the workspace root (`web/core/`, `web/feeder/`, `web/ops/`) instead. Reason: enables the ServeDir fallback chain to share core assets without duplication, and avoids coupling web resources to Rust crate build paths. This is a justified departure from the spec.
|
||
|
||
### What this plan does NOT cover (deferred)
|
||
|
||
- `PlatformContext` completion (filling in pool/connection_manager/event_manager/ws_manager)
|
||
- `config.rs` migration into shared core
|
||
- `control/validator.rs` splitting
|
||
- Event namespace migration at call sites
|
||
- These are larger refactors that should be planned separately
|