ework-web 0.10.32 → 0.10.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -642,11 +642,31 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
642
642
  if (!daemonEp && daemonIdParam) {
643
643
  const id = Number(daemonIdParam);
644
644
  if (Number.isFinite(id) && id > 0) {
645
- const ep = await resolveDaemonEndpoint(id);
645
+ const sessionMap = await getSessionDaemonMap();
646
+ const mapped = sessionMap.get(sid);
647
+ const effectiveId = mapped && mapped.daemonId !== id ? mapped.daemonId : id;
648
+ if (mapped && mapped.daemonId !== id) {
649
+ log.info(`session ${sid}: URL daemon_id=${id} but DB says daemon ${mapped.daemonId}, redirecting`);
650
+ }
651
+ const ep = await resolveDaemonEndpoint(effectiveId);
646
652
  if (ep) {
647
653
  const client = new RemoteOpencodeClient(ep);
648
- const { html: body } = await buildSessionView(client, sid, desc, cfg.collapseLines, limit, all);
649
- return html(body);
654
+ try {
655
+ const { html: body } = await buildSessionView(client, sid, desc, cfg.collapseLines, limit, all);
656
+ return html(body);
657
+ } catch (e) {
658
+ const status = e instanceof OpencodeError ? e.status : 500;
659
+ if (status === 404) {
660
+ return html(errorPage(
661
+ "会话不在该 daemon 上",
662
+ `会话 ${sid} 在 daemon ${effectiveId} (${ep}) 上找不到 (HTTP ${status})。\n\n` +
663
+ `URL 指定的 daemon_id=${id},数据库映射的 daemon=${mapped?.daemonId ?? "无"}。\n` +
664
+ `可能原因:daemon 重启后 ID 变化,或会话在另一个 daemon 上创建。\n\n` +
665
+ `可用 daemon 列表见 /sessions 页面。`,
666
+ ), 404);
667
+ }
668
+ throw e;
669
+ }
650
670
  }
651
671
  }
652
672
  }
@@ -682,7 +702,9 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
682
702
  if (ep) daemonParam = ep;
683
703
  }
684
704
  }
685
- if (daemonParam && !isLocalhost(daemonParam)) {
705
+ // daemon_id explicit → always browse via daemon API (multi-daemon localhost setup would 404 on local FS)
706
+ const daemonIdExplicit = url.searchParams.has("daemon_id");
707
+ if (daemonParam && (!isLocalhost(daemonParam) || daemonIdExplicit)) {
686
708
  try {
687
709
  const mode = url.searchParams.get("mode") ?? "tail";
688
710
  const order = url.searchParams.get("order") ?? "desc";
@@ -6,7 +6,7 @@ export const LIST_PAGE_SIZE = 50;
6
6
 
7
7
  function labelChip(label: LabelRow, owner: string, repo: string, state: string): string {
8
8
  const href = `/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?state=${state}&label=${encodeURIComponent(label.name)}`;
9
- return `<a class="issue-label" href="${escapeAttr(href)}" style="background:${escapeAttr(label.color)}" title="${escapeAttr(label.description ?? "")}">${escapeHtml(label.name)}</a>`;
9
+ return `<a class="issue-label" href="${escapeAttr(href)}" style="--lc:${escapeAttr(label.color)}" title="${escapeAttr(label.description ?? "")}">${escapeHtml(label.name)}</a>`;
10
10
  }
11
11
 
12
12
  function issueRow(
@@ -78,9 +78,10 @@ export async function buildIssueList(
78
78
  .row-title{font-weight:500;overflow-wrap:anywhere}
79
79
  .row-meta{color:var(--text-muted);font-size:12px;margin-top:.2rem}
80
80
  .empty{color:var(--text-muted);text-align:center;padding:2rem;font-size:13px}
81
- .row-labels{display:inline;gap:.2rem;margin-left:.4rem}
82
- .issue-label{display:inline-block;padding:0 5px;border-radius:6px;font-size:10px;font-weight:600;color:#fff;text-decoration:none;line-height:16px;white-space:nowrap;vertical-align:1px}
83
- .issue-label:hover{opacity:.85;text-decoration:none}
81
+ .row-labels{display:inline;margin-left:.3rem}
82
+ .issue-label{display:inline;color:var(--text-muted);font-size:11px;text-decoration:none;margin-left:.35rem;white-space:nowrap}
83
+ .issue-label::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--lc);margin-right:2px;vertical-align:middle}
84
+ .issue-label:hover{color:var(--text);text-decoration:underline}
84
85
  .label-filter{font-size:13px;color:var(--text-muted);margin-bottom:.4rem}
85
86
  .label-filter strong{color:var(--text)}
86
87
  .label-clear{color:var(--text-muted);text-decoration:none;margin-left:.3rem}
@@ -6,7 +6,7 @@ export const FEED_PAGE_SIZE = 50;
6
6
 
7
7
  function labelChip(label: LabelRow, owner: string, repo: string, state: string): string {
8
8
  const href = `/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?state=${state}&label=${encodeURIComponent(label.name)}`;
9
- return `<a class="issue-label" href="${escapeAttr(href)}" style="background:${escapeAttr(label.color)}" title="${escapeAttr(label.description ?? "")}">${escapeHtml(label.name)}</a>`;
9
+ return `<a class="issue-label" href="${escapeAttr(href)}" style="--lc:${escapeAttr(label.color)}" title="${escapeAttr(label.description ?? "")}">${escapeHtml(label.name)}</a>`;
10
10
  }
11
11
 
12
12
  function issueRow(it: IssueWithMeta, q: string, state: string, labels: LabelRow[]): string {
@@ -59,9 +59,10 @@ export async function buildIssuesFeed(
59
59
  .row-title{font-weight:500;overflow-wrap:anywhere}
60
60
  .row-meta{color:var(--text-muted);font-size:12px;margin-top:.2rem}
61
61
  .empty{color:var(--text-muted);text-align:center;padding:2rem;font-size:13px}
62
- .row-labels{display:inline;gap:.2rem;margin-left:.4rem}
63
- .issue-label{display:inline-block;padding:0 5px;border-radius:6px;font-size:10px;font-weight:600;color:#fff;text-decoration:none;line-height:16px;white-space:nowrap;vertical-align:1px}
64
- .issue-label:hover{opacity:.85;text-decoration:none}
62
+ .row-labels{display:inline;margin-left:.3rem}
63
+ .issue-label{display:inline;color:var(--text-muted);font-size:11px;text-decoration:none;margin-left:.35rem;white-space:nowrap}
64
+ .issue-label::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--lc);margin-right:2px;vertical-align:middle}
65
+ .issue-label:hover{color:var(--text);text-decoration:underline}
65
66
  .label-filter{font-size:13px;color:var(--text-muted);margin-bottom:.4rem}
66
67
  .label-filter strong{color:var(--text)}
67
68
  </style></head><body>