@things-factory/system 10.0.0-beta.8 → 10.0.0-beta.89

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.
@@ -7,7 +7,14 @@ import { PageView } from '@operato/shell';
7
7
  declare const SystemInfo_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
8
8
  export declare class SystemInfo extends SystemInfo_base {
9
9
  static styles: import("lit").CSSResult;
10
+ private _swStatus;
11
+ private _swLastChecked;
12
+ private _swChecking;
10
13
  private _applicationMeta?;
14
+ private _swRegistration;
15
+ private _swStateChangeListener;
16
+ private _swUpdateFoundListener;
17
+ private _observedSW;
11
18
  get context(): {
12
19
  title: string;
13
20
  };
@@ -18,7 +25,34 @@ export declare class SystemInfo extends SystemInfo_base {
18
25
  copyright: string;
19
26
  };
20
27
  render(): import("lit-html").TemplateResult<1>;
21
- onClickOpenSourceLicense(e: any): void;
22
- onClickLicenseInfo(e: any): void;
28
+ connectedCallback(): void;
29
+ disconnectedCallback(): void;
30
+ /**
31
+ * SW registration 과 installing/waiting 상태를 관찰.
32
+ * `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.
33
+ *
34
+ * 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를
35
+ * 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).
36
+ */
37
+ private _setupSwObserver;
38
+ /**
39
+ * 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후
40
+ * 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.
41
+ */
42
+ private _checkForUpdates;
43
+ private _bindWorker;
44
+ private _updateStatusFromWorker;
45
+ private _teardownSwObserver;
46
+ private _renderSwStatus;
47
+ /**
48
+ * "5초 전" / "2분 전" 같은 상대 시간 문자열.
49
+ */
50
+ private _formatAgo;
51
+ /**
52
+ * 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.
53
+ */
54
+ private _renderDevInfo;
55
+ onClickOpenSourceLicense(e: Event): void;
56
+ onClickLicenseInfo(e: Event): void;
23
57
  }
24
58
  export {};
@@ -1,17 +1,27 @@
1
- import { __decorate } from "tslib";
1
+ import { __decorate, __metadata } from "tslib";
2
2
  import '@material/web/icon/icon.js';
3
3
  import '@material/web/button/text-button.js';
4
4
  import '@operato/i18n/ox-i18n.js';
5
5
  import '../viewparts/opensource-license';
6
6
  import '../viewparts/license-info';
7
7
  import { css, html } from 'lit';
8
- import { customElement } from 'lit/decorators.js';
8
+ import { customElement, state } from 'lit/decorators.js';
9
9
  import { openHelp } from '@operato/help';
10
10
  import { i18next, localize } from '@operato/i18n';
11
11
  import { openPopup } from '@operato/layout';
12
12
  import { PageView } from '@operato/shell';
13
13
  const ENV = process.env;
14
14
  let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
15
+ constructor() {
16
+ super(...arguments);
17
+ this._swStatus = 'up-to-date';
18
+ this._swLastChecked = null;
19
+ this._swChecking = false;
20
+ this._swRegistration = null;
21
+ this._swStateChangeListener = null;
22
+ this._swUpdateFoundListener = null;
23
+ this._observedSW = null;
24
+ }
15
25
  static { this.styles = css `
16
26
  :host {
17
27
  display: block;
@@ -28,13 +38,6 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
28
38
  vertical-align: middle;
29
39
  }
30
40
 
31
- #refresh-button {
32
- border-radius: var(--border-radius);
33
- border: 1px solid rgba(0, 0, 0, 0.1);
34
-
35
- text-transform: capitalize;
36
- }
37
-
38
41
  div {
39
42
  padding: 10px;
40
43
  }
@@ -85,6 +88,92 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
85
88
  opacity: var(--help-icon-hover-opacity, 1);
86
89
  color: var(--help-icon-hover-color);
87
90
  }
91
+
92
+ /* Service Worker 상태 배너 */
93
+ .sw-status {
94
+ display: inline-flex;
95
+ align-items: center;
96
+ gap: 8px;
97
+ padding: 8px 14px;
98
+ margin: 8px auto;
99
+ border-radius: 6px;
100
+ font-size: 13px;
101
+ max-width: 420px;
102
+ }
103
+ .sw-status.downloading {
104
+ background: var(--sw-status-download-bg, #fef3c7);
105
+ color: var(--sw-status-download-color, #78350f);
106
+ border: 1px solid var(--sw-status-download-border, #fbbf24);
107
+ }
108
+ .sw-status.ready {
109
+ background: var(--sw-status-ready-bg, #dcfce7);
110
+ color: var(--sw-status-ready-color, #14532d);
111
+ border: 1px solid var(--sw-status-ready-border, #22c55e);
112
+ }
113
+ .sw-status.up-to-date {
114
+ background: var(--sw-status-uptodate-bg, #f0fdf4);
115
+ color: var(--sw-status-uptodate-color, #166534);
116
+ border: 1px solid var(--sw-status-uptodate-border, #86efac);
117
+ }
118
+ .sw-status.info {
119
+ background: var(--sw-status-info-bg, #eff6ff);
120
+ color: var(--sw-status-info-color, #1e3a8a);
121
+ border: 1px solid var(--sw-status-info-border, #93c5fd);
122
+ }
123
+ .sw-status-meta {
124
+ font-size: 11px;
125
+ opacity: 0.7;
126
+ }
127
+ .sw-check-btn {
128
+ margin-left: auto;
129
+ --md-text-button-label-text-size: 11px;
130
+ }
131
+
132
+ /* 개발 정보 블록 */
133
+ .dev-info {
134
+ max-width: 480px;
135
+ margin: 18px auto;
136
+ padding: 12px 16px;
137
+ border: 1px dashed #cbd5e1;
138
+ border-radius: 6px;
139
+ text-align: left;
140
+ background: rgba(148, 163, 184, 0.06);
141
+ }
142
+ .dev-info-title {
143
+ font-size: 11px;
144
+ font-weight: 700;
145
+ text-transform: uppercase;
146
+ letter-spacing: 1px;
147
+ color: #64748b;
148
+ margin-bottom: 8px;
149
+ }
150
+ .dev-info dl {
151
+ display: grid;
152
+ grid-template-columns: max-content 1fr;
153
+ gap: 4px 12px;
154
+ font-size: 12px;
155
+ margin: 0;
156
+ }
157
+ .dev-info dt {
158
+ color: #475569;
159
+ font-weight: 600;
160
+ }
161
+ .dev-info dd {
162
+ margin: 0;
163
+ color: #0f172a;
164
+ font-family: 'SF Mono', Consolas, monospace;
165
+ word-break: break-all;
166
+ }
167
+ .sw-status md-icon {
168
+ font-size: 18px;
169
+ }
170
+ .sw-status md-icon.spinning {
171
+ animation: sw-spin 1.4s linear infinite;
172
+ }
173
+ @keyframes sw-spin {
174
+ from { transform: rotate(0deg); }
175
+ to { transform: rotate(360deg); }
176
+ }
88
177
  `; }
89
178
  get context() {
90
179
  return {
@@ -93,10 +182,10 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
93
182
  }
94
183
  get applicationMeta() {
95
184
  if (!this._applicationMeta) {
96
- var iconLink = document.querySelector('link[rel="application-icon"]');
97
- var titleMeta = document.querySelector('meta[name="application-name"]');
98
- var descriptionMeta = document.querySelector('meta[name="application-description"]');
99
- var copyrightMeta = document.querySelector('meta[name="application-copyright"]');
185
+ const iconLink = document.querySelector('link[rel="application-icon"]');
186
+ const titleMeta = document.querySelector('meta[name="application-name"]');
187
+ const descriptionMeta = document.querySelector('meta[name="application-description"]');
188
+ const copyrightMeta = document.querySelector('meta[name="application-copyright"]');
100
189
  this._applicationMeta = {
101
190
  icon: iconLink?.href,
102
191
  title: titleMeta ? titleMeta.content : 'Things Factory',
@@ -107,7 +196,7 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
107
196
  return this._applicationMeta;
108
197
  }
109
198
  render() {
110
- var { icon, title, description, copyright } = this.applicationMeta;
199
+ const { icon, title, description, copyright } = this.applicationMeta;
111
200
  return html `
112
201
  <img src=${icon} alt="system logo" />
113
202
 
@@ -119,36 +208,233 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
119
208
  >
120
209
  </div>
121
210
 
122
- <div>
123
- <md-text-button
124
- id="refresh-button"
125
- @click=${e => {
126
- location.reload();
127
- }}
128
- >
129
- <md-icon>refresh</md-icon>
130
- ${String(i18next.t('label.system reload'))}
131
- </md-text-button>
132
- </div>
211
+ ${this._renderSwStatus()}
133
212
 
134
213
  <div>
135
214
  <span>${copyright}</span>
136
215
 
137
216
  <span
138
217
  >${title} is built on several
139
- <a href="#" @click=${e => this.onClickOpenSourceLicense(e)}>open source software</a></span
218
+ <a href="#" @click=${(e) => this.onClickOpenSourceLicense(e)}>open source software</a></span
140
219
  >
141
- <span>We are using the <a href="#" @click=${e => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>
220
+ <span>We are using the <a href="#" @click=${(e) => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>
142
221
  </div>
143
222
 
223
+ ${this._renderDevInfo()}
224
+
144
225
  <div class="poweredBy">
145
226
  <span
146
227
  >Powered by &trade;Things Factory <ox-i18n msgid="field.version"></ox-i18n> ${ENV['SHELL-VERSION']}
147
- <md-icon @click=${e => openHelp('about/things-factory')}>help</md-icon>
228
+ <md-icon @click=${() => openHelp('about/things-factory')}>help</md-icon>
148
229
  </span>
149
230
  </div>
150
231
  `;
151
232
  }
233
+ // ───────── Service Worker 상태 추적 ─────────
234
+ connectedCallback() {
235
+ super.connectedCallback();
236
+ this._setupSwObserver();
237
+ }
238
+ disconnectedCallback() {
239
+ super.disconnectedCallback();
240
+ this._teardownSwObserver();
241
+ }
242
+ /**
243
+ * SW registration 과 installing/waiting 상태를 관찰.
244
+ * `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.
245
+ *
246
+ * 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를
247
+ * 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).
248
+ */
249
+ async _setupSwObserver() {
250
+ // dev 미리보기 쇼트컷
251
+ const demo = new URLSearchParams(window.location.search).get('sw-demo');
252
+ if (demo === 'downloading' || demo === 'installing' || demo === 'ready' || demo === 'up-to-date') {
253
+ this._swStatus = demo;
254
+ this._swLastChecked = new Date();
255
+ return;
256
+ }
257
+ if (!('serviceWorker' in navigator)) {
258
+ this._swStatus = 'unsupported';
259
+ return;
260
+ }
261
+ const reg = await navigator.serviceWorker.getRegistration();
262
+ if (!reg) {
263
+ this._swStatus = 'unregistered';
264
+ return;
265
+ }
266
+ this._swRegistration = reg;
267
+ // 이미 installing 중인 SW 가 있으면 즉시 관찰
268
+ if (reg.installing)
269
+ this._bindWorker(reg.installing);
270
+ // 이미 waiting (설치 완료, 적용 대기) 이면 ready
271
+ if (reg.waiting)
272
+ this._swStatus = 'ready';
273
+ // 새 SW 감지 핸들러
274
+ this._swUpdateFoundListener = () => {
275
+ if (reg.installing)
276
+ this._bindWorker(reg.installing);
277
+ };
278
+ reg.addEventListener('updatefound', this._swUpdateFoundListener);
279
+ // 아직 새 SW 가 없으면 "up-to-date" 로 표시
280
+ if (!reg.installing && !reg.waiting) {
281
+ this._swStatus = 'up-to-date';
282
+ }
283
+ // 강제 update check — 페이지 열린 시점의 최신 여부 확인
284
+ await this._checkForUpdates();
285
+ }
286
+ /**
287
+ * 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후
288
+ * 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.
289
+ */
290
+ async _checkForUpdates() {
291
+ if (!this._swRegistration)
292
+ return;
293
+ this._swChecking = true;
294
+ try {
295
+ await this._swRegistration.update();
296
+ }
297
+ catch {
298
+ /* 네트워크 불가 등 — 조용히 무시 */
299
+ }
300
+ finally {
301
+ this._swChecking = false;
302
+ this._swLastChecked = new Date();
303
+ }
304
+ }
305
+ _bindWorker(sw) {
306
+ this._observedSW = sw;
307
+ this._updateStatusFromWorker(sw);
308
+ this._swStateChangeListener = () => this._updateStatusFromWorker(sw);
309
+ sw.addEventListener('statechange', this._swStateChangeListener);
310
+ }
311
+ _updateStatusFromWorker(sw) {
312
+ switch (sw.state) {
313
+ case 'installing':
314
+ this._swStatus = 'downloading';
315
+ break;
316
+ case 'installed':
317
+ // 기존 controller 가 있으면 새 버전 대기 상태 (업데이트), 없으면 최초 설치
318
+ this._swStatus = navigator.serviceWorker.controller ? 'ready' : 'up-to-date';
319
+ break;
320
+ case 'activating':
321
+ this._swStatus = 'installing';
322
+ break;
323
+ case 'activated':
324
+ case 'redundant':
325
+ this._swStatus = 'up-to-date';
326
+ break;
327
+ }
328
+ }
329
+ _teardownSwObserver() {
330
+ if (this._observedSW && this._swStateChangeListener) {
331
+ this._observedSW.removeEventListener('statechange', this._swStateChangeListener);
332
+ }
333
+ if (this._swRegistration && this._swUpdateFoundListener) {
334
+ this._swRegistration.removeEventListener('updatefound', this._swUpdateFoundListener);
335
+ }
336
+ this._observedSW = null;
337
+ this._swRegistration = null;
338
+ this._swStateChangeListener = null;
339
+ this._swUpdateFoundListener = null;
340
+ }
341
+ _renderSwStatus() {
342
+ const checkedAgo = this._swLastChecked
343
+ ? this._formatAgo(this._swLastChecked)
344
+ : null;
345
+ // 각 상태별 스타일/아이콘/메시지
346
+ const statusConfig = {
347
+ downloading: {
348
+ tone: 'downloading',
349
+ icon: 'cached',
350
+ spin: true,
351
+ msg: i18next.t('text.new version downloading')
352
+ },
353
+ installing: {
354
+ tone: 'downloading',
355
+ icon: 'cached',
356
+ spin: true,
357
+ msg: i18next.t('text.new version installing')
358
+ },
359
+ ready: {
360
+ tone: 'ready',
361
+ icon: 'system_update',
362
+ msg: i18next.t('text.new version ready to apply')
363
+ },
364
+ 'up-to-date': {
365
+ tone: 'up-to-date',
366
+ icon: 'check_circle',
367
+ msg: i18next.t('text.you are on the latest version')
368
+ },
369
+ unregistered: {
370
+ tone: 'info',
371
+ icon: 'info',
372
+ msg: i18next.t('text.service worker not registered (dev mode)')
373
+ },
374
+ unsupported: {
375
+ tone: 'info',
376
+ icon: 'info',
377
+ msg: i18next.t('text.service worker not supported')
378
+ }
379
+ };
380
+ const s = statusConfig[this._swStatus];
381
+ return html `
382
+ <div class="sw-status ${s.tone}">
383
+ <md-icon class=${s.spin ? 'spinning' : ''}>${s.icon}</md-icon>
384
+ <span>${s.msg}</span>
385
+ ${checkedAgo
386
+ ? html `<span class="sw-status-meta">· ${i18next.t('text.checked')} ${checkedAgo}</span>`
387
+ : ''}
388
+ ${this._swStatus === 'up-to-date' || this._swStatus === 'unregistered'
389
+ ? html `
390
+ <md-text-button
391
+ class="sw-check-btn"
392
+ ?disabled=${this._swChecking}
393
+ @click=${() => this._checkForUpdates()}
394
+ >
395
+ <md-icon>refresh</md-icon>
396
+ ${String(i18next.t('label.check now'))}
397
+ </md-text-button>
398
+ `
399
+ : ''}
400
+ </div>
401
+ `;
402
+ }
403
+ /**
404
+ * "5초 전" / "2분 전" 같은 상대 시간 문자열.
405
+ */
406
+ _formatAgo(d) {
407
+ const sec = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
408
+ if (sec < 60)
409
+ return `${sec}${i18next.t('text.seconds ago suffix')}`;
410
+ const min = Math.floor(sec / 60);
411
+ if (min < 60)
412
+ return `${min}${i18next.t('text.minutes ago suffix')}`;
413
+ const hr = Math.floor(min / 60);
414
+ return `${hr}${i18next.t('text.hours ago suffix')}`;
415
+ }
416
+ /**
417
+ * 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.
418
+ */
419
+ _renderDevInfo() {
420
+ const nodeEnv = (ENV['NODE-ENV'] || '').toString();
421
+ if (nodeEnv !== 'development' && !new URLSearchParams(location.search).has('sw-demo')) {
422
+ return '';
423
+ }
424
+ return html `
425
+ <div class="dev-info">
426
+ <div class="dev-info-title">${i18next.t('text.development info')}</div>
427
+ <dl>
428
+ <dt>NODE_ENV</dt><dd>${nodeEnv || '(unset)'}</dd>
429
+ <dt>APP-VERSION</dt><dd>${ENV['APP-VERSION']}</dd>
430
+ <dt>SHELL-VERSION</dt><dd>${ENV['SHELL-VERSION']}</dd>
431
+ <dt>SHELL-LICENSE</dt><dd>${ENV['SHELL-LICENSE']}</dd>
432
+ <dt>Service Worker</dt><dd>${this._swStatus}</dd>
433
+ <dt>User Agent</dt><dd>${navigator.userAgent}</dd>
434
+ </dl>
435
+ </div>
436
+ `;
437
+ }
152
438
  onClickOpenSourceLicense(e) {
153
439
  e.preventDefault();
154
440
  e.stopPropagation();
@@ -168,6 +454,18 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
168
454
  });
169
455
  }
170
456
  };
457
+ __decorate([
458
+ state(),
459
+ __metadata("design:type", String)
460
+ ], SystemInfo.prototype, "_swStatus", void 0);
461
+ __decorate([
462
+ state(),
463
+ __metadata("design:type", Object)
464
+ ], SystemInfo.prototype, "_swLastChecked", void 0);
465
+ __decorate([
466
+ state(),
467
+ __metadata("design:type", Boolean)
468
+ ], SystemInfo.prototype, "_swChecking", void 0);
171
469
  SystemInfo = __decorate([
172
470
  customElement('system-info')
173
471
  ], SystemInfo);
@@ -1 +1 @@
1
- {"version":3,"file":"system-info.js","sourceRoot":"","sources":["../../client/pages/system-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/i18n/ox-i18n.js'\nimport '../viewparts/opensource-license'\nimport '../viewparts/license-info'\n\nimport { css, html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { openHelp } from '@operato/help'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\n\nconst ENV = process.env\n\n@customElement('system-info')\nexport class SystemInfo extends localize(i18next)(PageView) {\n static styles = css`\n :host {\n display: block;\n position: relative;\n text-align: center;\n\n background-color: var(--system-info-background-color);\n padding: var(--system-info-content-padding);\n color: var(--system-info-color);\n font: var(--system-info-font);\n }\n\n :host * {\n vertical-align: middle;\n }\n\n #refresh-button {\n border-radius: var(--border-radius);\n border: 1px solid rgba(0, 0, 0, 0.1);\n\n text-transform: capitalize;\n }\n\n div {\n padding: 10px;\n }\n\n strong {\n font: var(--system-info-appname-font);\n color: var(--system-info-appname-color);\n }\n\n span {\n display: block;\n }\n\n span.description {\n padding: 3px 10px;\n font: var(--system-info-description-font);\n color: var(--system-info-description-color);\n }\n\n span.version {\n display: inline-block;\n border-radius: 5px;\n opacity: 0.8;\n background-color: var(--system-info-version-background-color);\n padding: 3px 10px;\n font: var(--system-info-version-font);\n color: var(--system-info-version-color);\n }\n\n .poweredBy {\n background-color: #efefef;\n position: absolute;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n padding: 7px 0;\n width: 100%;\n bottom: 0;\n text-align: center;\n }\n\n .poweredBy md-icon {\n opacity: var(--help-icon-opacity, 0.4);\n font-size: var(--help-icon-size, 18px);\n color: var(--help-icon-color);\n }\n\n .poweredBy md-icon:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity, 1);\n color: var(--help-icon-hover-color);\n }\n `\n\n private _applicationMeta?: { icon?: string; title: string; description: string; copyright: string }\n\n get context() {\n return {\n title: i18next.t('label.about-system')\n }\n }\n\n get applicationMeta() {\n if (!this._applicationMeta) {\n var iconLink = document.querySelector('link[rel=\"application-icon\"]') as HTMLLinkElement\n var titleMeta = document.querySelector('meta[name=\"application-name\"]') as HTMLMetaElement\n var descriptionMeta = document.querySelector('meta[name=\"application-description\"]') as HTMLMetaElement\n var copyrightMeta = document.querySelector('meta[name=\"application-copyright\"]') as HTMLMetaElement\n\n this._applicationMeta = {\n icon: iconLink?.href,\n title: titleMeta ? titleMeta.content : 'Things Factory',\n description: descriptionMeta ? descriptionMeta.content : 'Reimagining Software',\n copyright: copyrightMeta ? copyrightMeta.content : 'Copyright © hatiolab.com. All Rights Reserved.\" />'\n }\n }\n\n return this._applicationMeta\n }\n\n render() {\n var { icon, title, description, copyright } = this.applicationMeta\n\n return html`\n <img src=${icon!} alt=\"system logo\" />\n\n <div>\n <strong>${title}</strong>\n <span class=\"description\">${description}</span>\n <span class=\"version\"\n ><ox-i18n msgid=\"field.version\"></ox-i18n> : ${ENV['APP-VERSION']}-${ENV['NODE-ENV']}</span\n >\n </div>\n\n <div>\n <md-text-button\n id=\"refresh-button\"\n @click=${e => {\n location.reload()\n }}\n >\n <md-icon>refresh</md-icon>\n ${String(i18next.t('label.system reload'))}\n </md-text-button>\n </div>\n\n <div>\n <span>${copyright}</span>\n\n <span\n >${title} is built on several\n <a href=\"#\" @click=${e => this.onClickOpenSourceLicense(e)}>open source software</a></span\n >\n <span>We are using the <a href=\"#\" @click=${e => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>\n </div>\n\n <div class=\"poweredBy\">\n <span\n >Powered by &trade;Things Factory <ox-i18n msgid=\"field.version\"></ox-i18n> ${ENV['SHELL-VERSION']}\n <md-icon @click=${e => openHelp('about/things-factory')}>help</md-icon>\n </span>\n </div>\n `\n }\n\n onClickOpenSourceLicense(e) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <opensource-license></opensource-license> `, {\n backdrop: true,\n size: 'large',\n title: 'Open Source Licenses'\n })\n }\n\n onClickLicenseInfo(e) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <license-info></license-info> `, {\n backdrop: true,\n size: 'large',\n title: 'License Information'\n })\n }\n}\n"]}
1
+ {"version":3,"file":"system-info.js","sourceRoot":"","sources":["../../client/pages/system-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/i18n/ox-i18n.js'\nimport '../viewparts/opensource-license'\nimport '../viewparts/license-info'\n\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { openHelp } from '@operato/help'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\n\nconst ENV = process.env\n\n/**\n * Service Worker 상태. 다운로드/설치 중인지, 적용 대기 중인지, 조용한 상태인지 구분.\n * - 'unsupported': 브라우저가 SW 미지원\n * - 'unregistered': 등록된 SW 없음 (보통 개발 모드)\n * - 'up-to-date' : active SW 만 존재, 새 버전 없음 (최신)\n * - 'downloading': 새 SW 가 installing 상태 (네트워크에서 파일 받아오는 중)\n * - 'installing' : installing→installed 전이 직전 (마무리)\n * - 'ready' : installed + 기존 controller 있음 → 활성 대기 중 (update 존재)\n */\ntype SwStatus = 'unsupported' | 'unregistered' | 'up-to-date' | 'downloading' | 'installing' | 'ready'\n\n@customElement('system-info')\nexport class SystemInfo extends localize(i18next)(PageView) {\n static styles = css`\n :host {\n display: block;\n position: relative;\n text-align: center;\n\n background-color: var(--system-info-background-color);\n padding: var(--system-info-content-padding);\n color: var(--system-info-color);\n font: var(--system-info-font);\n }\n\n :host * {\n vertical-align: middle;\n }\n\n div {\n padding: 10px;\n }\n\n strong {\n font: var(--system-info-appname-font);\n color: var(--system-info-appname-color);\n }\n\n span {\n display: block;\n }\n\n span.description {\n padding: 3px 10px;\n font: var(--system-info-description-font);\n color: var(--system-info-description-color);\n }\n\n span.version {\n display: inline-block;\n border-radius: 5px;\n opacity: 0.8;\n background-color: var(--system-info-version-background-color);\n padding: 3px 10px;\n font: var(--system-info-version-font);\n color: var(--system-info-version-color);\n }\n\n .poweredBy {\n background-color: #efefef;\n position: absolute;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n padding: 7px 0;\n width: 100%;\n bottom: 0;\n text-align: center;\n }\n\n .poweredBy md-icon {\n opacity: var(--help-icon-opacity, 0.4);\n font-size: var(--help-icon-size, 18px);\n color: var(--help-icon-color);\n }\n\n .poweredBy md-icon:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity, 1);\n color: var(--help-icon-hover-color);\n }\n\n /* Service Worker 상태 배너 */\n .sw-status {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n margin: 8px auto;\n border-radius: 6px;\n font-size: 13px;\n max-width: 420px;\n }\n .sw-status.downloading {\n background: var(--sw-status-download-bg, #fef3c7);\n color: var(--sw-status-download-color, #78350f);\n border: 1px solid var(--sw-status-download-border, #fbbf24);\n }\n .sw-status.ready {\n background: var(--sw-status-ready-bg, #dcfce7);\n color: var(--sw-status-ready-color, #14532d);\n border: 1px solid var(--sw-status-ready-border, #22c55e);\n }\n .sw-status.up-to-date {\n background: var(--sw-status-uptodate-bg, #f0fdf4);\n color: var(--sw-status-uptodate-color, #166534);\n border: 1px solid var(--sw-status-uptodate-border, #86efac);\n }\n .sw-status.info {\n background: var(--sw-status-info-bg, #eff6ff);\n color: var(--sw-status-info-color, #1e3a8a);\n border: 1px solid var(--sw-status-info-border, #93c5fd);\n }\n .sw-status-meta {\n font-size: 11px;\n opacity: 0.7;\n }\n .sw-check-btn {\n margin-left: auto;\n --md-text-button-label-text-size: 11px;\n }\n\n /* 개발 정보 블록 */\n .dev-info {\n max-width: 480px;\n margin: 18px auto;\n padding: 12px 16px;\n border: 1px dashed #cbd5e1;\n border-radius: 6px;\n text-align: left;\n background: rgba(148, 163, 184, 0.06);\n }\n .dev-info-title {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: #64748b;\n margin-bottom: 8px;\n }\n .dev-info dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 4px 12px;\n font-size: 12px;\n margin: 0;\n }\n .dev-info dt {\n color: #475569;\n font-weight: 600;\n }\n .dev-info dd {\n margin: 0;\n color: #0f172a;\n font-family: 'SF Mono', Consolas, monospace;\n word-break: break-all;\n }\n .sw-status md-icon {\n font-size: 18px;\n }\n .sw-status md-icon.spinning {\n animation: sw-spin 1.4s linear infinite;\n }\n @keyframes sw-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `\n\n @state() private _swStatus: SwStatus = 'up-to-date'\n @state() private _swLastChecked: Date | null = null\n @state() private _swChecking: boolean = false\n\n private _applicationMeta?: { icon?: string; title: string; description: string; copyright: string }\n private _swRegistration: ServiceWorkerRegistration | null = null\n private _swStateChangeListener: (() => void) | null = null\n private _swUpdateFoundListener: (() => void) | null = null\n private _observedSW: ServiceWorker | null = null\n\n get context() {\n return {\n title: i18next.t('label.about-system')\n }\n }\n\n get applicationMeta() {\n if (!this._applicationMeta) {\n const iconLink = document.querySelector('link[rel=\"application-icon\"]') as HTMLLinkElement\n const titleMeta = document.querySelector('meta[name=\"application-name\"]') as HTMLMetaElement\n const descriptionMeta = document.querySelector('meta[name=\"application-description\"]') as HTMLMetaElement\n const copyrightMeta = document.querySelector('meta[name=\"application-copyright\"]') as HTMLMetaElement\n\n this._applicationMeta = {\n icon: iconLink?.href,\n title: titleMeta ? titleMeta.content : 'Things Factory',\n description: descriptionMeta ? descriptionMeta.content : 'Reimagining Software',\n copyright: copyrightMeta ? copyrightMeta.content : 'Copyright © hatiolab.com. All Rights Reserved.\" />'\n }\n }\n\n return this._applicationMeta\n }\n\n render() {\n const { icon, title, description, copyright } = this.applicationMeta\n\n return html`\n <img src=${icon!} alt=\"system logo\" />\n\n <div>\n <strong>${title}</strong>\n <span class=\"description\">${description}</span>\n <span class=\"version\"\n ><ox-i18n msgid=\"field.version\"></ox-i18n> : ${ENV['APP-VERSION']}-${ENV['NODE-ENV']}</span\n >\n </div>\n\n ${this._renderSwStatus()}\n\n <div>\n <span>${copyright}</span>\n\n <span\n >${title} is built on several\n <a href=\"#\" @click=${(e: Event) => this.onClickOpenSourceLicense(e)}>open source software</a></span\n >\n <span>We are using the <a href=\"#\" @click=${(e: Event) => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>\n </div>\n\n ${this._renderDevInfo()}\n\n <div class=\"poweredBy\">\n <span\n >Powered by &trade;Things Factory <ox-i18n msgid=\"field.version\"></ox-i18n> ${ENV['SHELL-VERSION']}\n <md-icon @click=${() => openHelp('about/things-factory')}>help</md-icon>\n </span>\n </div>\n `\n }\n\n // ───────── Service Worker 상태 추적 ─────────\n\n connectedCallback() {\n super.connectedCallback()\n this._setupSwObserver()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._teardownSwObserver()\n }\n\n /**\n * SW registration 과 installing/waiting 상태를 관찰.\n * `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.\n *\n * 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를\n * 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).\n */\n private async _setupSwObserver() {\n // dev 미리보기 쇼트컷\n const demo = new URLSearchParams(window.location.search).get('sw-demo')\n if (demo === 'downloading' || demo === 'installing' || demo === 'ready' || demo === 'up-to-date') {\n this._swStatus = demo as SwStatus\n this._swLastChecked = new Date()\n return\n }\n\n if (!('serviceWorker' in navigator)) {\n this._swStatus = 'unsupported'\n return\n }\n const reg = await navigator.serviceWorker.getRegistration()\n if (!reg) {\n this._swStatus = 'unregistered'\n return\n }\n this._swRegistration = reg\n\n // 이미 installing 중인 SW 가 있으면 즉시 관찰\n if (reg.installing) this._bindWorker(reg.installing)\n // 이미 waiting (설치 완료, 적용 대기) 이면 ready\n if (reg.waiting) this._swStatus = 'ready'\n\n // 새 SW 감지 핸들러\n this._swUpdateFoundListener = () => {\n if (reg.installing) this._bindWorker(reg.installing)\n }\n reg.addEventListener('updatefound', this._swUpdateFoundListener)\n\n // 아직 새 SW 가 없으면 \"up-to-date\" 로 표시\n if (!reg.installing && !reg.waiting) {\n this._swStatus = 'up-to-date'\n }\n\n // 강제 update check — 페이지 열린 시점의 최신 여부 확인\n await this._checkForUpdates()\n }\n\n /**\n * 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후\n * 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.\n */\n private async _checkForUpdates() {\n if (!this._swRegistration) return\n this._swChecking = true\n try {\n await this._swRegistration.update()\n } catch {\n /* 네트워크 불가 등 — 조용히 무시 */\n } finally {\n this._swChecking = false\n this._swLastChecked = new Date()\n }\n }\n\n private _bindWorker(sw: ServiceWorker) {\n this._observedSW = sw\n this._updateStatusFromWorker(sw)\n\n this._swStateChangeListener = () => this._updateStatusFromWorker(sw)\n sw.addEventListener('statechange', this._swStateChangeListener)\n }\n\n private _updateStatusFromWorker(sw: ServiceWorker) {\n switch (sw.state) {\n case 'installing':\n this._swStatus = 'downloading'\n break\n case 'installed':\n // 기존 controller 가 있으면 새 버전 대기 상태 (업데이트), 없으면 최초 설치\n this._swStatus = navigator.serviceWorker.controller ? 'ready' : 'up-to-date'\n break\n case 'activating':\n this._swStatus = 'installing'\n break\n case 'activated':\n case 'redundant':\n this._swStatus = 'up-to-date'\n break\n }\n }\n\n private _teardownSwObserver() {\n if (this._observedSW && this._swStateChangeListener) {\n this._observedSW.removeEventListener('statechange', this._swStateChangeListener)\n }\n if (this._swRegistration && this._swUpdateFoundListener) {\n this._swRegistration.removeEventListener('updatefound', this._swUpdateFoundListener)\n }\n this._observedSW = null\n this._swRegistration = null\n this._swStateChangeListener = null\n this._swUpdateFoundListener = null\n }\n\n private _renderSwStatus() {\n const checkedAgo = this._swLastChecked\n ? this._formatAgo(this._swLastChecked)\n : null\n\n // 각 상태별 스타일/아이콘/메시지\n const statusConfig: Record<\n SwStatus,\n { tone: string; icon: string; spin?: boolean; msg: string }\n > = {\n downloading: {\n tone: 'downloading',\n icon: 'cached',\n spin: true,\n msg: i18next.t('text.new version downloading')\n },\n installing: {\n tone: 'downloading',\n icon: 'cached',\n spin: true,\n msg: i18next.t('text.new version installing')\n },\n ready: {\n tone: 'ready',\n icon: 'system_update',\n msg: i18next.t('text.new version ready to apply')\n },\n 'up-to-date': {\n tone: 'up-to-date',\n icon: 'check_circle',\n msg: i18next.t('text.you are on the latest version')\n },\n unregistered: {\n tone: 'info',\n icon: 'info',\n msg: i18next.t('text.service worker not registered (dev mode)')\n },\n unsupported: {\n tone: 'info',\n icon: 'info',\n msg: i18next.t('text.service worker not supported')\n }\n }\n const s = statusConfig[this._swStatus]\n\n return html`\n <div class=\"sw-status ${s.tone}\">\n <md-icon class=${s.spin ? 'spinning' : ''}>${s.icon}</md-icon>\n <span>${s.msg}</span>\n ${checkedAgo\n ? html`<span class=\"sw-status-meta\">· ${i18next.t('text.checked')} ${checkedAgo}</span>`\n : ''}\n ${this._swStatus === 'up-to-date' || this._swStatus === 'unregistered'\n ? html`\n <md-text-button\n class=\"sw-check-btn\"\n ?disabled=${this._swChecking}\n @click=${() => this._checkForUpdates()}\n >\n <md-icon>refresh</md-icon>\n ${String(i18next.t('label.check now'))}\n </md-text-button>\n `\n : ''}\n </div>\n `\n }\n\n /**\n * \"5초 전\" / \"2분 전\" 같은 상대 시간 문자열.\n */\n private _formatAgo(d: Date): string {\n const sec = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000))\n if (sec < 60) return `${sec}${i18next.t('text.seconds ago suffix')}`\n const min = Math.floor(sec / 60)\n if (min < 60) return `${min}${i18next.t('text.minutes ago suffix')}`\n const hr = Math.floor(min / 60)\n return `${hr}${i18next.t('text.hours ago suffix')}`\n }\n\n /**\n * 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.\n */\n private _renderDevInfo() {\n const nodeEnv = (ENV['NODE-ENV'] || '').toString()\n if (nodeEnv !== 'development' && !new URLSearchParams(location.search).has('sw-demo')) {\n return ''\n }\n return html`\n <div class=\"dev-info\">\n <div class=\"dev-info-title\">${i18next.t('text.development info')}</div>\n <dl>\n <dt>NODE_ENV</dt><dd>${nodeEnv || '(unset)'}</dd>\n <dt>APP-VERSION</dt><dd>${ENV['APP-VERSION']}</dd>\n <dt>SHELL-VERSION</dt><dd>${ENV['SHELL-VERSION']}</dd>\n <dt>SHELL-LICENSE</dt><dd>${ENV['SHELL-LICENSE']}</dd>\n <dt>Service Worker</dt><dd>${this._swStatus}</dd>\n <dt>User Agent</dt><dd>${navigator.userAgent}</dd>\n </dl>\n </div>\n `\n }\n\n onClickOpenSourceLicense(e: Event) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <opensource-license></opensource-license> `, {\n backdrop: true,\n size: 'large',\n title: 'Open Source Licenses'\n })\n }\n\n onClickLicenseInfo(e: Event) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <license-info></license-info> `, {\n backdrop: true,\n size: 'large',\n title: 'License Information'\n })\n }\n}\n"]}
@@ -1 +1 @@
1
- export default function route(page: any): any;
1
+ export default function route(page: string): "system" | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAI;IAChC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC7B,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page) {\n switch (page) {\n case 'system':\n import('./pages/system-info')\n return page\n }\n}\n"]}
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC7B,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'system':\n import('./pages/system-info')\n return page\n }\n}\n"]}