@simplysm/sd-cli 14.0.95 → 14.0.97

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.
Files changed (99) hide show
  1. package/dist/commands/init/generators/client.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client.js +12 -0
  3. package/dist/commands/init/generators/client.js.map +1 -1
  4. package/dist/commands/init/generators/server.d.ts.map +1 -1
  5. package/dist/commands/init/generators/server.js +1 -0
  6. package/dist/commands/init/generators/server.js.map +1 -1
  7. package/dist/commands/init/normalize.d.ts.map +1 -1
  8. package/dist/commands/init/normalize.js +1 -0
  9. package/dist/commands/init/normalize.js.map +1 -1
  10. package/dist/commands/init/prompts.d.ts.map +1 -1
  11. package/dist/commands/init/prompts.js +8 -1
  12. package/dist/commands/init/prompts.js.map +1 -1
  13. package/dist/commands/init/types.d.ts +3 -0
  14. package/dist/commands/init/types.d.ts.map +1 -1
  15. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  16. package/dist/engines/EsbuildClientEngine.js +1 -0
  17. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  18. package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
  19. package/dist/esbuild/esbuild-client-config.js +2 -11
  20. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  21. package/dist/esbuild/esbuild-postcss-plugin.d.ts +4 -0
  22. package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -1
  23. package/dist/esbuild/esbuild-postcss-plugin.js +15 -0
  24. package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -1
  25. package/dist/esbuild/esbuild-ssr-config.d.ts +27 -0
  26. package/dist/esbuild/esbuild-ssr-config.d.ts.map +1 -0
  27. package/dist/esbuild/esbuild-ssr-config.js +113 -0
  28. package/dist/esbuild/esbuild-ssr-config.js.map +1 -0
  29. package/dist/sd-config.types.d.ts +7 -0
  30. package/dist/sd-config.types.d.ts.map +1 -1
  31. package/dist/ssg/prerender.d.ts +19 -0
  32. package/dist/ssg/prerender.d.ts.map +1 -0
  33. package/dist/ssg/prerender.js +43 -0
  34. package/dist/ssg/prerender.js.map +1 -0
  35. package/dist/workers/client.worker.d.ts +2 -0
  36. package/dist/workers/client.worker.d.ts.map +1 -1
  37. package/dist/workers/client.worker.js +21 -0
  38. package/dist/workers/client.worker.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/commands/init/generators/client.ts +53 -0
  41. package/src/commands/init/generators/server.ts +5 -0
  42. package/src/commands/init/normalize.ts +1 -0
  43. package/src/commands/init/prompts.ts +9 -1
  44. package/src/commands/init/templates/client/package.json.hbs +2 -1
  45. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +1 -1
  46. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.detail.ts.hbs +221 -0
  47. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +106 -0
  48. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +277 -0
  49. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +537 -0
  50. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +337 -0
  51. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +540 -0
  52. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +4 -6
  53. package/src/commands/init/templates/client/src/app/home/system/data-log/data-log.list.ts.hbs +355 -0
  54. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +382 -0
  55. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +3 -4
  56. package/src/commands/init/templates/client/src/app.root.ts.hbs +9 -3
  57. package/src/commands/init/templates/client/src/index.html.hbs +1 -0
  58. package/src/commands/init/templates/client/src/main.server.ts.hbs +24 -0
  59. package/src/commands/init/templates/client/src/main.ts.hbs +36 -18
  60. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +30 -0
  61. package/src/commands/init/templates/client/src/routes.ts.hbs +22 -0
  62. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -4
  63. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +3 -3
  64. package/src/commands/init/templates/client-common/src/providers/app-orm.provider.ts.hbs +0 -11
  65. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +24 -10
  66. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +2 -2
  67. package/src/commands/init/templates/common/package.json.hbs +2 -1
  68. package/src/commands/init/templates/common/src/app-structure.ts.hbs +7 -2
  69. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -1
  70. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +2 -2
  71. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +2 -2
  72. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +1 -0
  73. package/src/commands/init/templates/common/src/index.ts.hbs +1 -1
  74. package/src/commands/init/templates/server/src/index.ts.hbs +3 -0
  75. package/src/commands/init/templates/server/src/main.ts.hbs +15 -1
  76. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +28 -22
  77. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +5 -5
  78. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +191 -0
  79. package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +3 -0
  80. package/src/commands/init/types.ts +3 -0
  81. package/src/engines/EsbuildClientEngine.ts +1 -0
  82. package/src/esbuild/esbuild-client-config.ts +2 -12
  83. package/src/esbuild/esbuild-postcss-plugin.ts +18 -0
  84. package/src/esbuild/esbuild-ssr-config.ts +149 -0
  85. package/src/sd-config.types.ts +7 -0
  86. package/src/ssg/prerender.ts +65 -0
  87. package/src/workers/client.worker.ts +26 -0
  88. package/tests/engines/base-engine.spec.ts +1 -26
  89. package/tests/init/__snapshots__/render.spec.ts.snap +38 -20
  90. package/tests/init/render.spec.ts +113 -33
  91. package/tests/utils/hmr-client-script.acc.spec.ts +0 -21
  92. package/tests/angular/vite-angular-plugin.spec.ts +0 -102
  93. package/tests/engines/engine-adapter-isolation.spec.ts +0 -79
  94. package/tests/runtime/signal-handler.spec.ts +0 -21
  95. package/tests/utils/angular-build.spec.ts +0 -109
  96. package/tests/utils/esbuild-client-config.acc.spec.ts +0 -438
  97. package/tests/utils/esbuild-client-config.spec.ts +0 -659
  98. package/tests/utils/hmr-client-script.spec.ts +0 -44
  99. package/tests/utils/tsconfig-angular.spec.ts +0 -9
@@ -0,0 +1,382 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ effect,
5
+ inject,
6
+ signal,
7
+ untracked,
8
+ ViewEncapsulation,
9
+ } from "@angular/core";
10
+ import {
11
+ FormatPipe,
12
+ injectPermsSignal,
13
+ injectViewTitleSignal,
14
+ injectViewTypeSignal,
15
+ mark,
16
+ SdAnchor,
17
+ SdButton,
18
+ SdCrudList,
19
+ SdDateRangePicker,
20
+ SdItemOfTemplate,
21
+ SdLabel,
22
+ SdModalProvider,
23
+ SdSelect,
24
+ SdSelectItem,
25
+ SdSharedDataSelect,
26
+ SdSheetColumn,
27
+ SdSheetColumnCellTemplate,
28
+ SdTextfield,
29
+ SdToastProvider,
30
+ type SortingDef,
31
+ } from "@simplysm/angular";
32
+ import { expr } from "@simplysm/orm-common";
33
+ import { DateOnly, DateTime, obj } from "@simplysm/core-common";
34
+ import { downloadBlob } from "@simplysm/core-browser";
35
+ import { ExcelWrapper } from "@simplysm/excel";
36
+ import { z } from "zod";
37
+ import { AppOrmProvider, useSharedSignal } from "@{{workspaceName}}/client-common";
38
+ import { NgIcon } from "@ng-icons/core";
39
+ import { tablerDownload } from "@ng-icons/tabler-icons";
40
+ import { TextViewModal } from "../../../../modals/text-view.modal";
41
+
42
+ const ITEMS_PER_PAGE = 50;
43
+
44
+ type TSeverity = "error" | "warn" | "log";
45
+
46
+ @Component({
47
+ selector: "app-system-log-list",
48
+ changeDetection: ChangeDetectionStrategy.OnPush,
49
+ encapsulation: ViewEncapsulation.None,
50
+ standalone: true,
51
+ imports: [
52
+ SdCrudList,
53
+ SdSheetColumn,
54
+ SdSheetColumnCellTemplate,
55
+ SdTextfield,
56
+ SdDateRangePicker,
57
+ SdSelect,
58
+ SdSelectItem,
59
+ SdSharedDataSelect,
60
+ SdItemOfTemplate,
61
+ SdLabel,
62
+ SdAnchor,
63
+ SdButton,
64
+ NgIcon,
65
+ FormatPipe,
66
+ ],
67
+ template: `
68
+ <sd-crud-list
69
+ [key]="'system-log-list'"
70
+ [(ready)]="ready"
71
+ [initialized]="initialized()"
72
+ [(busyCount)]="busyCount"
73
+ [viewType]="viewType()"
74
+ [restricted]="!perms().includes('use')"
75
+ [readonly]="true"
76
+ [inlineEdit]="false"
77
+ [items]="items()"
78
+ [trackByFn]="trackByFn"
79
+ [(currentPage)]="page"
80
+ [totalPageCount]="pageLength()"
81
+ [(sorts)]="sortingDefs"
82
+ (filterSubmit)="onFilterSubmit()"
83
+ >
84
+ <ng-template #filterTpl>
85
+ <div class="form-box-inline">
86
+ <div>
87
+ <label>기간</label>
88
+ <sd-date-range-picker
89
+ [(from)]="filter().fromDate"
90
+ (fromChange)="mark(filter)"
91
+ [(to)]="filter().toDate"
92
+ (toChange)="mark(filter)"
93
+ />
94
+ </div>
95
+ <div>
96
+ <label>심각도</label>
97
+ <sd-select [(value)]="filter().severity" (valueChange)="mark(filter)">
98
+ <sd-select-item [value]="undefined">
99
+ <span class="tx-theme-gray-default">전체</span>
100
+ </sd-select-item>
101
+ <sd-select-item [value]="'error'">
102
+ <sd-label [theme]="'danger'">error</sd-label>
103
+ </sd-select-item>
104
+ <sd-select-item [value]="'warn'">
105
+ <sd-label [theme]="'warning'">warn</sd-label>
106
+ </sd-select-item>
107
+ <sd-select-item [value]="'log'">
108
+ <sd-label [theme]="'gray'">log</sd-label>
109
+ </sd-select-item>
110
+ </sd-select>
111
+ </div>
112
+ <div>
113
+ <label>검색어</label>
114
+ <sd-textfield
115
+ [type]="'text'"
116
+ [(value)]="filter().searchText"
117
+ (valueChange)="mark(filter)"
118
+ />
119
+ </div>
120
+ <div>
121
+ <label>사용자</label>
122
+ <sd-shared-data-select
123
+ [selectMode]="'multi'"
124
+ [items]="shared{{userEntityPascal}}s.items()"
125
+ [(value)]="filter().{{userEntityCamel}}Ids"
126
+ (valueChange)="mark(filter)"
127
+ >
128
+ <ng-template [itemOf]="shared{{userEntityPascal}}s.items()" let-item="item">
129
+ \{{ item.name }}
130
+ </ng-template>
131
+ </sd-shared-data-select>
132
+ </div>
133
+ </div>
134
+ </ng-template>
135
+
136
+ <ng-template #toolTpl>
137
+ <sd-button [size]="'sm'" [theme]="'link-success'" (click)="onDownloadExcelButtonClick()">
138
+ <ng-icon [svg]="tablerDownload" />
139
+ 엑셀 다운로드
140
+ </sd-button>
141
+ </ng-template>
142
+
143
+ <sd-sheet-column [key]="'id'" [header]="'#'">
144
+ <ng-template [cell]="items()" let-item="item">
145
+ <div class="p-xs-sm tx-right">\{{ item.id }}</div>
146
+ </ng-template>
147
+ </sd-sheet-column>
148
+
149
+ <sd-sheet-column [key]="'dateTime'" [header]="'발생시각'">
150
+ <ng-template [cell]="items()" let-item="item">
151
+ <div class="p-xs-sm">\{{ item.dateTime | format: "yyyy-MM-dd HH:mm:ss" }}</div>
152
+ </ng-template>
153
+ </sd-sheet-column>
154
+
155
+ <sd-sheet-column [key]="'severity'" [header]="'심각도'">
156
+ <ng-template [cell]="items()" let-item="item">
157
+ <div class="p-xs-sm">
158
+ <sd-label [theme]="severityTheme(item.severity)">\{{ item.severity }}</sd-label>
159
+ </div>
160
+ </ng-template>
161
+ </sd-sheet-column>
162
+
163
+ <sd-sheet-column [key]="'clientName'" [header]="'클라이언트'">
164
+ <ng-template [cell]="items()" let-item="item">
165
+ <div class="p-xs-sm">\{{ item.clientName }}</div>
166
+ </ng-template>
167
+ </sd-sheet-column>
168
+
169
+ <sd-sheet-column [key]="'{{userEntityCamel}}Name'" [header]="'사용자'">
170
+ <ng-template [cell]="items()" let-item="item">
171
+ <div class="p-xs-sm">\{{ item.{{userEntityCamel}}Name ?? "&nbsp;" }}</div>
172
+ </ng-template>
173
+ </sd-sheet-column>
174
+
175
+ <sd-sheet-column [key]="'message'" [header]="'메시지'">
176
+ <ng-template [cell]="items()" let-item="item">
177
+ @if (item.message) {
178
+ <sd-anchor (click)="onOpenMessage(item)">
179
+ <div
180
+ class="p-xs-sm"
181
+ style="max-width: 40em; overflow: clip; white-space: nowrap; text-overflow: ellipsis;"
182
+ >
183
+ \{{ item.message }}
184
+ </div>
185
+ </sd-anchor>
186
+ } @else {
187
+ <div class="p-xs-sm">&nbsp;</div>
188
+ }
189
+ </ng-template>
190
+ </sd-sheet-column>
191
+ </sd-crud-list>
192
+ `,
193
+ })
194
+ export class SystemLogList {
195
+ private readonly _sdToast = inject(SdToastProvider);
196
+ private readonly _sdModal = inject(SdModalProvider);
197
+ private readonly _appOrm = inject(AppOrmProvider);
198
+
199
+ perms = injectPermsSignal(["system.system-log"], ["use"]);
200
+ viewType = injectViewTypeSignal();
201
+ viewTitle = injectViewTitleSignal();
202
+
203
+ ready = signal(false);
204
+ initialized = signal(false);
205
+ busyCount = signal(0);
206
+
207
+ shared{{userEntityPascal}}s = useSharedSignal("{{userEntityLabel}}");
208
+
209
+ items = signal<IItem[]>([]);
210
+ page = signal(0);
211
+ pageLength = signal(0);
212
+ sortingDefs = signal<SortingDef[]>([]);
213
+
214
+ filter = signal<IFilter>({ {{userEntityCamel}}Ids: [] });
215
+ lastFilter = signal<IFilter>({ {{userEntityCamel}}Ids: [] });
216
+
217
+ trackByFn = (item: IItem): number => item.id;
218
+
219
+ private readonly _excelWrapper = new ExcelWrapper(
220
+ z.object({
221
+ dateTime: z.custom<DateTime>().optional().describe("발생시각"),
222
+ severity: z.string().optional().describe("심각도"),
223
+ clientName: z.string().optional().describe("클라이언트"),
224
+ {{userEntityCamel}}Name: z.string().optional().describe("사용자"),
225
+ message: z.string().optional().describe("메시지"),
226
+ }),
227
+ );
228
+
229
+ constructor() {
230
+ effect(() => {
231
+ if (!this.perms().includes("use") || !this.ready()) {
232
+ this.initialized.set(true);
233
+ return;
234
+ }
235
+
236
+ this.lastFilter();
237
+ this.page();
238
+ this.sortingDefs();
239
+
240
+ void untracked(async () => {
241
+ this.busyCount.update((v) => v + 1);
242
+ await this._sdToast.try(async () => {
243
+ await this._refresh();
244
+ });
245
+ this.busyCount.update((v) => v - 1);
246
+ this.initialized.set(true);
247
+ });
248
+ });
249
+ }
250
+
251
+ onFilterSubmit(): void {
252
+ this.page.set(0);
253
+ this.lastFilter.set({ ...this.filter() });
254
+ }
255
+
256
+ severityTheme(severity: string): "danger" | "warning" | "gray" {
257
+ if (severity === "error") return "danger";
258
+ if (severity === "warn") return "warning";
259
+ return "gray";
260
+ }
261
+
262
+ async onOpenMessage(item: IItem): Promise<void> {
263
+ if (item.message == null || item.message === "") return;
264
+ await this._sdModal.showAsync({
265
+ type: TextViewModal,
266
+ title: "메시지",
267
+ inputs: { text: item.message },
268
+ });
269
+ }
270
+
271
+ async onDownloadExcelButtonClick(): Promise<void> {
272
+ if (this.busyCount() > 0) return;
273
+
274
+ this.busyCount.update((v) => v + 1);
275
+ await this._sdToast.try(async () => {
276
+ const r = await this._search(false);
277
+ const records = r.items.map((item) => ({
278
+ dateTime: item.dateTime,
279
+ severity: item.severity,
280
+ clientName: item.clientName,
281
+ {{userEntityCamel}}Name: item.{{userEntityCamel}}Name,
282
+ message: item.message,
283
+ }));
284
+ const wb = await this._excelWrapper.write(this.viewTitle(), records);
285
+ try {
286
+ downloadBlob(
287
+ await wb.toBlob(),
288
+ `${this.viewTitle()}_${new DateTime().toFormatString("yyMMdd")}.xlsx`,
289
+ );
290
+ } finally {
291
+ await wb.close();
292
+ }
293
+ });
294
+ this.busyCount.update((v) => v - 1);
295
+ }
296
+
297
+ private async _refresh(): Promise<void> {
298
+ const r = await this._search(true);
299
+ this.items.set(r.items);
300
+ this.pageLength.set(r.pageLength);
301
+ }
302
+
303
+ private async _search(usePagination: boolean): Promise<{ items: IItem[]; pageLength: number }> {
304
+ return this._appOrm.connectAsync(async (db) => {
305
+ let qr = db.systemLog();
306
+
307
+ const fromDate = this.lastFilter().fromDate;
308
+ if (fromDate != null) {
309
+ const fromBoundary = new DateTime(fromDate.year, fromDate.month, fromDate.day);
310
+ qr = qr.where((c) => [expr.gte(c.dateTime, fromBoundary)]);
311
+ }
312
+
313
+ const toDate = this.lastFilter().toDate;
314
+ if (toDate != null) {
315
+ const toBoundary = new DateTime(toDate.year, toDate.month, toDate.day).addDays(1);
316
+ qr = qr.where((c) => [expr.lt(c.dateTime, toBoundary)]);
317
+ }
318
+
319
+ const severity = this.lastFilter().severity;
320
+ if (severity != null) {
321
+ qr = qr.where((c) => [expr.eq(c.severity, severity)]);
322
+ }
323
+
324
+ const searchText = this.lastFilter().searchText?.trim();
325
+ if (searchText != null && searchText.length > 0) {
326
+ qr = qr.search((c) => [c.clientName, c.message], searchText);
327
+ }
328
+
329
+ const {{userEntityCamel}}Ids = this.lastFilter().{{userEntityCamel}}Ids;
330
+ if ({{userEntityCamel}}Ids.length > 0) {
331
+ qr = qr.where((c) => [expr.in(c.{{userEntityCamel}}Id, {{userEntityCamel}}Ids)]);
332
+ }
333
+
334
+ const pageLength = usePagination ? Math.ceil((await qr.count()) / ITEMS_PER_PAGE) : 0;
335
+
336
+ let qr2 = qr
337
+ .include((c) => c.{{userEntityCamel}})
338
+ .select((c) => ({
339
+ id: c.id,
340
+ dateTime: c.dateTime,
341
+ severity: c.severity,
342
+ clientName: c.clientName,
343
+ message: c.message,
344
+ {{userEntityCamel}}Name: c.{{userEntityCamel}}?.name,
345
+ }));
346
+
347
+ for (const sort of this.sortingDefs()) {
348
+ qr2 = qr2.orderBy((c) => obj.getChainValue(c, sort.key) as any, sort.desc ? "DESC" : "ASC");
349
+ }
350
+ if (!this.sortingDefs().some((s) => s.key === "id")) {
351
+ qr2 = qr2.orderBy((c) => c.id, "DESC");
352
+ }
353
+
354
+ if (usePagination) {
355
+ qr2 = qr2.limit(this.page() * ITEMS_PER_PAGE, ITEMS_PER_PAGE);
356
+ }
357
+
358
+ const items = (await qr2.execute()) as IItem[];
359
+ return { items, pageLength };
360
+ });
361
+ }
362
+
363
+ protected readonly mark = mark;
364
+ protected readonly tablerDownload = tablerDownload;
365
+ }
366
+
367
+ interface IItem {
368
+ id: number;
369
+ dateTime: DateTime;
370
+ severity: string;
371
+ clientName: string;
372
+ message?: string;
373
+ {{userEntityCamel}}Name?: string;
374
+ }
375
+
376
+ interface IFilter {
377
+ fromDate?: DateOnly;
378
+ toDate?: DateOnly;
379
+ severity?: TSeverity;
380
+ searchText?: string;
381
+ {{userEntityCamel}}Ids: number[];
382
+ }
@@ -108,7 +108,7 @@ export class LoginView {
108
108
  effect(() => {
109
109
  void untracked(async () => {
110
110
  this.initBusyCount.update((v) => v + 1);
111
- try {
111
+ await this._sdToast.try(async () => {
112
112
  const ok = await this._appAuth.tryReloadAuth();
113
113
  if (ok) {
114
114
  const firstRouterLink = this._appAuth.authInfo()?.configs["first-router-link"] ?? "/home/main";
@@ -119,9 +119,8 @@ export class LoginView {
119
119
  if (lastLoginId != null && lastLoginId !== "") {
120
120
  this.data.update((d) => ({ ...d, loginId: lastLoginId }));
121
121
  }
122
- } finally {
123
- this.initBusyCount.update((v) => v - 1);
124
- }
122
+ });
123
+ this.initBusyCount.update((v) => v - 1);
125
124
  });
126
125
  });
127
126
  }
@@ -17,12 +17,18 @@ import { DevModal } from "./modals/dev.modal";
17
17
  changeDetection: ChangeDetectionStrategy.OnPush,
18
18
  encapsulation: ViewEncapsulation.None,
19
19
  standalone: true,
20
+ {{#if client.hasRouter}}
21
+ imports: [RouterOutlet],
22
+ {{/if}}
20
23
  {{#if hasDb}}
21
- host: { "(document:keydown)": "onKeydown($event)" },
24
+ host: {
25
+ "(document:keydown)": "onKeydown($event)",
26
+ },
22
27
  {{/if}}
23
28
  {{#if client.hasRouter}}
24
- imports: [RouterOutlet],
25
- template: `<router-outlet />`,
29
+ template: `
30
+ <router-outlet />
31
+ `,
26
32
  {{else}}
27
33
  template: `<div></div>`,
28
34
  {{/if}}
@@ -6,6 +6,7 @@
6
6
  <meta name="format-detection" content="telephone=no" />
7
7
  <meta name="msapplication-tap-highlight" content="no" />
8
8
  <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
9
+
9
10
  <title>{{description}}</title>
10
11
  </head>
11
12
  <body>
@@ -0,0 +1,24 @@
1
+ import { bootstrapApplication, type BootstrapContext } from "@angular/platform-browser";
2
+ import { provideServerRendering } from "@angular/platform-server";
3
+ import { provideHttpClient, withFetch } from "@angular/common/http";
4
+ import { provideRouter } from "@angular/router";
5
+ import { provideSdAngular } from "@simplysm/angular";
6
+ import { AppRoot } from "./app.root";
7
+ import { routes } from "./routes";
8
+
9
+ // 프리렌더(빌드 시점) 전용 부트스트랩 — 서버 연결·로그 배선 등 브라우저 전용 초기화는 제외
10
+ const bootstrap = (context: BootstrapContext) =>
11
+ bootstrapApplication(
12
+ AppRoot,
13
+ {
14
+ providers: [
15
+ provideHttpClient(withFetch()),
16
+ provideRouter(routes),
17
+ provideSdAngular({ clientName: "{{client.name}}" }),
18
+ provideServerRendering(),
19
+ ],
20
+ },
21
+ context,
22
+ );
23
+
24
+ export default bootstrap;
@@ -3,52 +3,67 @@ import { enableProdMode, inject, provideAppInitializer } from "@angular/core";
3
3
  {{else}}
4
4
  import { enableProdMode } from "@angular/core";
5
5
  {{/if}}
6
+ {{#if client.useSsg}}
7
+ import { bootstrapApplication, provideClientHydration } from "@angular/platform-browser";
8
+ {{else}}
6
9
  import { bootstrapApplication } from "@angular/platform-browser";
10
+ {{/if}}
7
11
  import { provideHttpClient, withFetch } from "@angular/common/http";
8
12
  {{#if client.hasRouter}}
13
+ {{#if client.useSsg}}
14
+ import { provideRouter } from "@angular/router";
15
+ {{else}}
9
16
  import { provideRouter, withHashLocation } from "@angular/router";
10
17
  {{/if}}
18
+ {{/if}}
11
19
  import {
12
20
  provideSdAngular,
21
+ {{#if hasAuth}}
22
+ {{#if client.hasRouter}}
23
+ SdAppStructureProvider,
24
+ {{/if}}
25
+ {{/if}}
13
26
  {{#if hasDb}}
14
27
  SdSharedDataProvider,
15
- SdSystemLogProvider,
16
28
  {{/if}}
17
29
  {{#if hasAuth}}
18
30
  SdSystemConfigProvider,
19
- {{#if client.hasRouter}}
20
- SdAppStructureProvider,
21
31
  {{/if}}
32
+ {{#if hasDb}}
33
+ SdSystemLogProvider,
22
34
  {{/if}}
23
35
  } from "@simplysm/angular";
24
36
  import { createLogger{{#if hasDb}}, DateTime, json{{/if}} } from "@simplysm/core-common";
37
+ import { AppRoot } from "./app.root";
38
+ {{#if client.hasRouter}}
39
+ import { routes } from "./routes";
40
+ {{/if}}
41
+ import "./styles.scss";
25
42
  {{#if hasAuth}}
26
- import { expr } from "@simplysm/orm-common";
43
+ {{#if client.hasRouter}}
44
+ import { {{client.appStructureName}} } from "@{{workspaceName}}/common";
45
+ {{/if}}
27
46
  {{/if}}
28
47
  {{#if hasServer}}
29
48
  import {
30
- AppServiceProvider,
49
+ {{#if hasAuth}}
50
+ AppAuthProvider,
51
+ {{/if}}
31
52
  {{#if hasDb}}
32
53
  AppOrmProvider,
33
- AppSharedDataProvider,
34
54
  {{/if}}
35
- {{#if hasAuth}}
36
- AppAuthProvider,
55
+ AppServiceProvider,
56
+ {{#if hasDb}}
57
+ AppSharedDataProvider,
37
58
  {{/if}}
38
59
  } from "@{{workspaceName}}/client-common";
39
60
  {{/if}}
40
61
  {{#if hasAuth}}
41
- {{#if client.hasRouter}}
42
- import { {{client.appStructureName}} } from "@{{workspaceName}}/common";
43
- {{/if}}
44
- {{/if}}
45
- import { AppRoot } from "./app.root";
46
- {{#if client.hasRouter}}
47
- import { routes } from "./routes";
62
+ import { expr } from "@simplysm/orm-common";
48
63
  {{/if}}
49
- import "./styles.scss";
50
64
 
51
65
  const CLIENT_NAME = "{{client.name}}";
66
+
52
67
  const logger = createLogger(CLIENT_NAME);
53
68
 
54
69
  if (typeof ngDevMode !== "undefined" && !ngDevMode) {
@@ -61,7 +76,12 @@ bootstrapApplication(AppRoot, {
61
76
  providers: [
62
77
  provideHttpClient(withFetch()),
63
78
  {{#if client.hasRouter}}
79
+ {{#if client.useSsg}}
80
+ provideRouter(routes),
81
+ provideClientHydration(),
82
+ {{else}}
64
83
  provideRouter(routes, withHashLocation()),
84
+ {{/if}}
65
85
  {{/if}}
66
86
  provideSdAngular({ clientName: CLIENT_NAME }),
67
87
  {{#if hasDb}}
@@ -70,7 +90,6 @@ bootstrapApplication(AppRoot, {
70
90
  {{/if}}
71
91
  {{#if hasServer}}
72
92
 
73
- //-- app-service 연결
74
93
  provideAppInitializer(async () => {
75
94
  await inject(AppServiceProvider).connectAsync();
76
95
  }),
@@ -78,7 +97,6 @@ bootstrapApplication(AppRoot, {
78
97
  {{#if hasAuth}}
79
98
  {{#if client.hasRouter}}
80
99
 
81
- //-- app-structure 초기화
82
100
  provideAppInitializer(() => {
83
101
  inject(SdAppStructureProvider).initialize({{client.appStructureName}});
84
102
  }),
@@ -0,0 +1,30 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ input,
5
+ output,
6
+ signal,
7
+ ViewEncapsulation,
8
+ } from "@angular/core";
9
+ import { type SdModalContentDef } from "@simplysm/angular";
10
+
11
+ @Component({
12
+ selector: "app-text-view-modal",
13
+ changeDetection: ChangeDetectionStrategy.OnPush,
14
+ encapsulation: ViewEncapsulation.None,
15
+ standalone: true,
16
+ imports: [],
17
+ template: `
18
+ <div class="p-default">
19
+ <div class="p-default bg-theme-gray-lightest bd-radius-default bd bd-theme-gray-lighter">
20
+ <pre>\{{ text() }}</pre>
21
+ </div>
22
+ </div>
23
+ `,
24
+ })
25
+ export class TextViewModal implements SdModalContentDef<void> {
26
+ text = input.required<string>();
27
+ close = output<void>();
28
+
29
+ initialized = signal(true);
30
+ }
@@ -21,6 +21,28 @@ export const routes: Routes = [
21
21
  loadComponent: () =>
22
22
  import("./app/home/my-info/my-info.detail").then((m) => m.MyInfoDetail),
23
23
  },
24
+ {
25
+ path: "master/role-permission",
26
+ loadComponent: () =>
27
+ import("./app/home/master/role-permission/role-permission.view").then(
28
+ (m) => m.RolePermissionView,
29
+ ),
30
+ },
31
+ {
32
+ path: "master/{{userEntityKebab}}",
33
+ loadComponent: () =>
34
+ import("./app/home/master/{{userEntityKebab}}/{{userEntityKebab}}.list").then((m) => m.{{userEntityPascal}}List),
35
+ },
36
+ {
37
+ path: "system/data-log",
38
+ loadComponent: () =>
39
+ import("./app/home/system/data-log/data-log.list").then((m) => m.DataLogList),
40
+ },
41
+ {
42
+ path: "system/system-log",
43
+ loadComponent: () =>
44
+ import("./app/home/system/system-log/system-log.list").then((m) => m.SystemLogList),
45
+ },
24
46
  ],
25
47
  },
26
48
  ];
@@ -1,13 +1,15 @@
1
+ {{#if hasAuth}}
2
+ export * from "./providers/app-auth.provider";
3
+ {{/if}}
4
+ {{#if hasDb}}
5
+ export * from "./providers/app-orm.provider";
6
+ {{/if}}
1
7
  {{#if hasServer}}
2
8
  export * from "./providers/app-service.provider";
3
9
  {{/if}}
4
10
  {{#if hasDb}}
5
- export * from "./providers/app-orm.provider";
6
11
  export * from "./providers/app-shared-data.provider";
7
12
  {{/if}}
8
- {{#if hasAuth}}
9
- export * from "./providers/app-auth.provider";
10
- {{/if}}
11
13
  {{#unless hasServer}}
12
14
  {{#unless hasDb}}
13
15
  export {};
@@ -56,7 +56,7 @@ export class AppAuthProvider {
56
56
  await this._appService.client.auth(token);
57
57
  this.authInfo.set(authData);
58
58
  this._sdAppStructure.permRecord.set(authData.permissions);
59
- await this._registerAuthEvent(authData.{{userEntityCamel}}Id);
59
+ await this._registerAuthEvent(authData.{{userEntityCamel}}Id, authData.roleId);
60
60
  this._appSharedData.initialize();
61
61
  }
62
62
 
@@ -67,10 +67,10 @@ export class AppAuthProvider {
67
67
  this._sdAppStructure.permRecord.set(undefined);
68
68
  }
69
69
 
70
- private async _registerAuthEvent({{userEntityCamel}}Id: number): Promise<void> {
70
+ private async _registerAuthEvent({{userEntityCamel}}Id: number, roleId: number): Promise<void> {
71
71
  await this._unregisterAuthEvent();
72
72
  this._authEventKey = await this._appService.authInfoChangedEvent.addListener(
73
- { {{userEntityCamel}}Id },
73
+ { {{userEntityCamel}}Id, roleId },
74
74
  async () => {
75
75
  await this.tryReloadAuth();
76
76
  },
@@ -16,15 +16,4 @@ export class AppOrmProvider {
16
16
  callback,
17
17
  );
18
18
  }
19
-
20
- connectWithoutTransAsync<R>(callback: (db: {{dbContextClassName}}) => Promise<R>): Promise<R> {
21
- return this._appService.orm.connectWithoutTransaction(
22
- {
23
- DbClass: {{dbContextClassName}},
24
- connOpt: { configName: "{{dbContextNameUpper}}" },
25
- dbContextOpt: { database: "{{workspaceNameUpper}}" },
26
- },
27
- callback,
28
- );
29
- }
30
19
  }