ework-web 0.4.0 → 0.4.2

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.4.0",
3
+ "version": "0.4.2",
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
@@ -240,8 +240,16 @@ function daemonEnvPath(): string | null {
240
240
  const p = join(dataDir, ".env");
241
241
  return existsSync(p) ? p : null;
242
242
  }
243
- const conventional = join(homedir(), ".local", "share", "ework-daemon", ".env");
244
- return existsSync(conventional) ? conventional : null;
243
+ // Conventional paths: standalone install, then ework-aio managed layout
244
+ // (~/.local/share/ework-aio/ework-daemon/.env).
245
+ const candidates = [
246
+ join(homedir(), ".local", "share", "ework-daemon", ".env"),
247
+ join(homedir(), ".local", "share", "ework-aio", "ework-daemon", ".env"),
248
+ ];
249
+ for (const c of candidates) {
250
+ if (existsSync(c)) return c;
251
+ }
252
+ return null;
245
253
  }
246
254
 
247
255
  function spawnDaemonRestart(): void {
@@ -370,6 +378,7 @@ function chunkTextTTS(text: string, max = 120): string[] {
370
378
 
371
379
  async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean; user: UserRow | null }): Promise<Response> {
372
380
  if (url.pathname === "/static/app.js") return staticAsset("app.js", "text/javascript; charset=utf-8", req);
381
+ if (url.pathname === "/static/db-wizard.js") return staticAsset("db-wizard.js", "text/javascript; charset=utf-8", req);
373
382
  if (url.pathname === "/static/session.js") return staticAsset("session.js", "text/javascript; charset=utf-8", req);
374
383
  if (url.pathname === "/static/file.js") return staticAsset("file.js", "text/javascript; charset=utf-8", req);
375
384
  if (url.pathname === "/static/tts.js") return staticAsset("tts.js", "text/javascript; charset=utf-8", req);
@@ -0,0 +1,106 @@
1
+ // DB-backend migration wizard (settings page). External file because CSP
2
+ // script-src 'self' blocks inline scripts — see index.ts SEC_HEADERS.
3
+ (function () {
4
+ if (!document.getElementById('db-test')) return;
5
+ var R = document.getElementById('db-result');
6
+
7
+ function get(id) { return document.getElementById(id).value; }
8
+ function gather() {
9
+ return JSON.stringify({
10
+ host: get('db-host').trim(),
11
+ port: Number(get('db-port')),
12
+ user: get('db-user').trim(),
13
+ password: get('db-password'),
14
+ database: get('db-database').trim(),
15
+ prefix: get('db-prefix').trim()
16
+ });
17
+ }
18
+ function setBtns(d) {
19
+ document.querySelectorAll('.db-controls button').forEach(function (b) { b.disabled = d; });
20
+ }
21
+
22
+ async function act(action) {
23
+ if (action === 'enable' && !confirm('确认启用 MySQL 并重启?确保已完成 ① 测试 + ② 迁移。')) return;
24
+ setBtns(true);
25
+ R.className = 'db-result db-loading';
26
+ R.textContent = action === 'enable' ? '正在写入 .env,进程即将重启…' : '处理中…';
27
+ try {
28
+ var res = await fetch('/api/db/' + action, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: gather()
32
+ });
33
+ var data = await res.json();
34
+ if (action === 'enable' && data.ok) {
35
+ R.className = 'db-result db-ok';
36
+ R.textContent = '✓ .env 已写入。进程重启中,等待恢复…';
37
+ setTimeout(poll, 3000);
38
+ return;
39
+ }
40
+ if (action === 'daemon-config') {
41
+ R.className = 'db-result ' + (data.configured ? 'db-ok' : 'db-err');
42
+ R.textContent = data.configured
43
+ ? '✓ daemon 已写入 .env 并重启:\n ' + data.envPath
44
+ : '⚠ daemon 无法自动配置(.env 未找到)。请手动操作:\n\n' + (data.manual || data.error || '');
45
+ return;
46
+ }
47
+ R.className = 'db-result ' + (data.ok ? 'db-ok' : 'db-err');
48
+ if (!data.ok) {
49
+ var m = '✗ ' + (data.error || '未知错误');
50
+ if (data.hint) m += '\n💡 ' + data.hint;
51
+ R.textContent = m;
52
+ } else if (data.tables) {
53
+ R.textContent = '✓ 迁移完成(' + data.tables.length + ' 张表):\n' +
54
+ data.tables.map(function (t) { return ' ' + t.table + ': ' + t.rows + ' 行'; }).join('\n');
55
+ } else if (data.serverVersion) {
56
+ R.textContent = '✓ 连接成功 · MySQL ' + data.serverVersion +
57
+ (data.databaseExists ? '' : '(库不存在,迁移时自动创建)');
58
+ }
59
+ } catch (e) {
60
+ R.className = 'db-result db-err';
61
+ R.textContent = '✗ ' + (e.message || String(e));
62
+ } finally {
63
+ if (action !== 'enable') setBtns(false);
64
+ }
65
+ }
66
+
67
+ async function poll() {
68
+ try { await fetch('/'); location.href = '/settings?saved=1'; }
69
+ catch (e) { setTimeout(poll, 2000); }
70
+ }
71
+
72
+ document.getElementById('db-test').onclick = function () { act('test'); };
73
+ document.getElementById('db-migrate').onclick = function () { act('migrate'); };
74
+ document.getElementById('db-daemon').onclick = function () { act('daemon-config'); };
75
+ document.getElementById('db-enable').onclick = function () { act('enable'); };
76
+
77
+ var revertBtn = document.getElementById('db-revert');
78
+ if (revertBtn) {
79
+ revertBtn.onclick = async function () {
80
+ if (!confirm('确认切回 SQLite?将创建新的 SQLite 文件并迁移数据,然后重启。')) return;
81
+ var Rr = document.getElementById('db-revert-result');
82
+ Rr.className = 'db-result db-loading';
83
+ Rr.textContent = '正在迁移到 SQLite…';
84
+ try {
85
+ var res = await fetch('/api/db/revert', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: '{}'
89
+ });
90
+ var data = await res.json();
91
+ if (data.ok) {
92
+ Rr.className = 'db-result db-ok';
93
+ Rr.textContent = '✓ 已迁移到 ' + data.targetPath + '。进程重启中…';
94
+ setTimeout(poll, 3000);
95
+ } else {
96
+ Rr.className = 'db-result db-err';
97
+ Rr.textContent = '✗ ' + (data.error || '未知错误') +
98
+ (data.partial ? '\n部分完成: ' + data.partial.join(', ') : '');
99
+ }
100
+ } catch (e) {
101
+ Rr.className = 'db-result db-err';
102
+ Rr.textContent = '✗ ' + (e.message || String(e));
103
+ }
104
+ };
105
+ }
106
+ })();
@@ -63,49 +63,7 @@ function buildDbSection(viewer: UserRow): string {
63
63
  </div>
64
64
  <div id="db-result" class="db-result"></div>
65
65
  ${isMysql ? `<hr style="border:0;border-top:1px solid var(--border);margin:1rem 0"><div class="db-controls"><button type="button" id="db-revert" class="secondary">⚠ 切回 SQLite(安全网)</button></div><div id="db-revert-result" class="db-result"></div>` : ""}
66
- <script>
67
- (function(){
68
- var R=document.getElementById('db-result');
69
- function get(id){return document.getElementById(id).value;}
70
- function gather(){return JSON.stringify({host:get('db-host').trim(),port:Number(get('db-port')),user:get('db-user').trim(),password:get('db-password'),database:get('db-database').trim(),prefix:get('db-prefix').trim()});}
71
- function setBtns(d){document.querySelectorAll('.db-controls button').forEach(function(b){b.disabled=d;});}
72
- async function act(action){
73
- if(action==='enable'&&!confirm('确认启用 MySQL 并重启?确保已完成 ① 测试 + ② 迁移。'))return;
74
- setBtns(true);
75
- R.className='db-result db-loading';
76
- R.textContent=action==='enable'?'正在写入 .env,进程即将重启…':'处理中…';
77
- try{
78
- var res=await fetch('/api/db/'+action,{method:'POST',headers:{'Content-Type':'application/json'},body:gather()});
79
- var data=await res.json();
80
- if(action==='enable'&&data.ok){R.className='db-result db-ok';R.textContent='✓ .env 已写入。进程重启中,等待恢复…';setTimeout(poll,3000);return;}
81
- if(action==='daemon-config'){R.className='db-result '+(data.configured?'db-ok':'db-err');R.textContent=data.configured?'✓ daemon 已写入 .env 并重启:\\n '+data.envPath:'⚠ daemon 无法自动配置(.env 未找到)。请手动操作:\\n\\n'+(data.manual||data.error||'');return;}
82
- R.className='db-result '+(data.ok?'db-ok':'db-err');
83
- if(!data.ok){var m='✗ '+(data.error||'未知错误');if(data.hint)m+='\\n💡 '+data.hint;R.textContent=m;}
84
- else if(data.tables){R.textContent='✓ 迁移完成('+data.tables.length+' 张表):\\n'+data.tables.map(function(t){return' '+t.table+': '+t.rows+' 行';}).join('\\n');}
85
- else if(data.serverVersion){R.textContent='✓ 连接成功 · MySQL '+data.serverVersion+(data.databaseExists?'':'(库不存在,迁移时自动创建)');}
86
- }catch(e){R.className='db-result db-err';R.textContent='✗ '+(e.message||String(e));}
87
- finally{if(action!=='enable')setBtns(false);}
88
- }
89
- async function poll(){try{await fetch('/');location.href='/settings?saved=1';}catch(e){setTimeout(poll,2000);}}
90
- document.getElementById('db-test').onclick=function(){act('test');};
91
- document.getElementById('db-migrate').onclick=function(){act('migrate');};
92
- document.getElementById('db-daemon').onclick=function(){act('daemon-config');};
93
- document.getElementById('db-enable').onclick=function(){act('enable');};
94
- if(document.getElementById('db-revert')){
95
- document.getElementById('db-revert').onclick=async function(){
96
- if(!confirm('确认切回 SQLite?将创建新的 SQLite 文件并迁移数据,然后重启。'))return;
97
- var R=document.getElementById('db-revert-result');
98
- R.className='db-result db-loading';R.textContent='正在迁移到 SQLite…';
99
- try{
100
- var res=await fetch('/api/db/revert',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});
101
- var data=await res.json();
102
- if(data.ok){R.className='db-result db-ok';R.textContent='✓ 已迁移到 '+data.targetPath+'。进程重启中…';setTimeout(poll,3000);}
103
- else{R.className='db-result db-err';R.textContent='✗ '+(data.error||'未知错误')+(data.partial?'\\n部分完成: '+data.partial.join(', '):'');}
104
- }catch(e){R.className='db-result db-err';R.textContent='✗ '+(e.message||String(e));}
105
- };
106
- }
107
- })();
108
- </script>
66
+ <script src="/static/db-wizard.js"></script>
109
67
  </section>`;
110
68
  }
111
69