@dssp/dcsp 1.0.2 → 1.0.6
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/dist-client/bootstrap.js +13 -10
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
- package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
- package/dist-client/components/kpi-boxplot-chart.js +306 -0
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
- package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
- package/dist-client/components/kpi-lookup-chart.js +464 -0
- package/dist-client/components/kpi-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
- package/dist-client/components/kpi-mini-trend-chart.js +180 -0
- package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
- package/dist-client/components/kpi-radar-chart.d.ts +17 -0
- package/dist-client/components/kpi-radar-chart.js +259 -0
- package/dist-client/components/kpi-radar-chart.js.map +1 -0
- package/dist-client/components/kpi-single-boxplot-chart.d.ts +36 -0
- package/dist-client/components/kpi-single-boxplot-chart.js +417 -0
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
- package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
- package/dist-client/components/kpi-step-lookup-chart.js +181 -0
- package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-trend-chart.d.ts +25 -0
- package/dist-client/components/kpi-trend-chart.js +241 -0
- package/dist-client/components/kpi-trend-chart.js.map +1 -0
- package/dist-client/google-map/common-google-map.d.ts +35 -0
- package/dist-client/google-map/common-google-map.js +349 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +23 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +69 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +790 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +22 -0
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +344 -0
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +21 -0
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +335 -0
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
- package/dist-client/pages/sv-project-complete.d.ts +31 -0
- package/dist-client/pages/sv-project-complete.js +364 -0
- package/dist-client/pages/sv-project-complete.js.map +1 -0
- package/dist-client/pages/sv-project-detail.d.ts +73 -0
- package/dist-client/pages/sv-project-detail.js +1387 -0
- package/dist-client/pages/sv-project-detail.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +15 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +45 -0
- package/dist-client/shared/complete-api.js +274 -0
- package/dist-client/shared/complete-api.js.map +1 -0
- package/dist-client/shared/func.d.ts +2 -0
- package/dist-client/shared/func.js +22 -0
- package/dist-client/shared/func.js.map +1 -0
- package/dist-client/shared/integration-fetch.d.ts +35 -0
- package/dist-client/shared/integration-fetch.js +53 -0
- package/dist-client/shared/integration-fetch.js.map +1 -0
- package/dist-client/shared/kpi-project-sync.d.ts +16 -0
- package/dist-client/shared/kpi-project-sync.js +129 -0
- package/dist-client/shared/kpi-project-sync.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/menu-tools.d.ts +7 -0
- package/dist-client/viewparts/menu-tools.js +168 -21
- package/dist-client/viewparts/menu-tools.js.map +1 -1
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +5 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/index.d.ts +7 -0
- package/dist-server/middlewares/index.js +100 -0
- package/dist-server/middlewares/index.js.map +1 -0
- package/dist-server/service/index.d.ts +4 -0
- package/dist-server/service/index.js +19 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
- package/dist-server/service/kpi-metric-value/index.js +8 -0
- package/dist-server/service/kpi-metric-value/index.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +198 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +1067 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +15 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +72 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
- package/dist-server/service/kpi-stat/index.d.ts +4 -0
- package/dist-server/service/kpi-stat/index.js +8 -0
- package/dist-server/service/kpi-stat/index.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js +126 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
- package/dist-server/service/kpi-value/index.d.ts +3 -0
- package/dist-server/service/kpi-value/index.js +7 -0
- package/dist-server/service/kpi-value/index.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
- package/dist-server/service/kpi-value/kpi-value-query.js +90 -0
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/python/RUNNER_CONVERSION_GUIDE.md +81 -0
- package/python/doc_summary_engine_handoff/.env.example +21 -0
- package/python/doc_summary_engine_handoff/HANDOFF.md +266 -0
- package/python/doc_summary_engine_handoff/README.md +71 -0
- package/python/doc_summary_engine_handoff/__main__.py +159 -0
- package/python/doc_summary_engine_handoff/api.py +165 -0
- package/python/doc_summary_engine_handoff/extract.py +290 -0
- package/python/doc_summary_engine_handoff/requirements.txt +15 -0
- package/python/doc_summary_engine_handoff/summarize.py +247 -0
- package/python/inspection_ai_engine_handoff/.env.example +22 -0
- package/python/inspection_ai_engine_handoff/HANDOFF.md +541 -0
- package/python/inspection_ai_engine_handoff/README.md +83 -0
- package/python/inspection_ai_engine_handoff/__main__.py +169 -0
- package/python/inspection_ai_engine_handoff/activities.py +147 -0
- package/python/inspection_ai_engine_handoff/api.py +273 -0
- package/python/inspection_ai_engine_handoff/checklist.py +53 -0
- package/python/inspection_ai_engine_handoff/cv_handlers.py +46 -0
- package/python/inspection_ai_engine_handoff/cv_model_registry.py +252 -0
- package/python/inspection_ai_engine_handoff/cv_models/README.md +58 -0
- package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/handler.py +39 -0
- package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/model.json +22 -0
- package/python/inspection_ai_engine_handoff/engine.py +333 -0
- package/python/inspection_ai_engine_handoff/requirements.txt +12 -0
- package/python/inspection_ai_engine_handoff/router.py +200 -0
- package/python/inspection_ai_engine_handoff/shot_catalog.py +79 -0
- package/python/inspection_ai_engine_handoff/shot_planner.py +278 -0
- package/python/inspection_ai_engine_handoff/vlm.py +335 -0
- package/python/spec-matching-0.5.5/README.md +113 -0
- package/python/spec-matching-0.5.5/__main__.py +331 -0
- package/python/spec-matching-0.5.5/kcs.csv +6028 -0
- package/python/spec-matching-0.5.5/kcs_embeddings.meta.json +1 -0
- package/python/spec-matching-0.5.5/kcs_embeddings.npy +0 -0
- package/python/spec-matching-0.5.5/requirements.txt +3 -0
- package/python/spec-matching-0.5.5//342/230/2057 SF-TD4 /341/204/221/341/205/263/341/204/205/341/205/251/341/204/200/341/205/263/341/204/205/341/205/242/341/206/267/341/204/211/341/205/245/341/206/257/341/204/200/341/205/250/341/204/211/341/205/245 /341/204/213/341/205/243/341/206/274/341/204/211/341/205/265/341/206/250_V1.0.hwp +0 -0
- package/schema.graphql +1274 -22
- package/things-factory.config.js +7 -1
- package/translations/en.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
1
2
|
import { LitElement } from 'lit';
|
|
2
3
|
declare const MenuTools_base: (new (...args: any[]) => {
|
|
3
4
|
_storeUnsubscribe: () => void;
|
|
@@ -9,8 +10,14 @@ export declare class MenuTools extends MenuTools_base {
|
|
|
9
10
|
static styles: import("lit").CSSResult[];
|
|
10
11
|
page?: string;
|
|
11
12
|
width?: string;
|
|
13
|
+
openedMenuIndex: number | null;
|
|
14
|
+
/** 뮤테이션 가능 여부('dcsp guest' 권한 보유 시 false) — 게스트에게는 셋팅 메뉴를 숨긴다 */
|
|
15
|
+
canMutate: boolean;
|
|
12
16
|
private menus;
|
|
13
17
|
render(): import("lit-html").TemplateResult<1>;
|
|
18
|
+
connectedCallback(): void;
|
|
19
|
+
disconnectedCallback(): void;
|
|
20
|
+
private _closeSubmenu;
|
|
14
21
|
stateChanged(state: any): void;
|
|
15
22
|
}
|
|
16
23
|
export {};
|
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
2
3
|
import { css, html, LitElement } from 'lit';
|
|
3
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
5
|
import { store, connect } from '@operato/shell';
|
|
5
|
-
import {
|
|
6
|
+
import { canDcspMutate } from '@dssp/building-complex/dist-client/utils/privilege';
|
|
7
|
+
import { ICONS_PROGRESS, ICONS_COMPLETED, ICONS_STATUS, ICONS_SETTING } from '../icons/menu-icons';
|
|
6
8
|
let MenuTools = class MenuTools extends connect(store)(LitElement) {
|
|
7
9
|
constructor() {
|
|
8
10
|
super(...arguments);
|
|
11
|
+
this.openedMenuIndex = null;
|
|
12
|
+
/** 뮤테이션 가능 여부('dcsp guest' 권한 보유 시 false) — 게스트에게는 셋팅 메뉴를 숨긴다 */
|
|
13
|
+
this.canMutate = false;
|
|
9
14
|
this.menus = [];
|
|
15
|
+
this._closeSubmenu = (e) => {
|
|
16
|
+
const path = e.composedPath();
|
|
17
|
+
if (path.includes(this)) {
|
|
18
|
+
// 내부 클릭이라도 실제 페이지로 이동하는 링크 클릭이면 닫는다.
|
|
19
|
+
const isNavLink = path.some(el => el instanceof HTMLAnchorElement &&
|
|
20
|
+
!!el.getAttribute('href') &&
|
|
21
|
+
el.getAttribute('href') !== 'javascript:void(0)');
|
|
22
|
+
if (!isNavLink)
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.openedMenuIndex = null;
|
|
26
|
+
};
|
|
10
27
|
}
|
|
11
28
|
render() {
|
|
29
|
+
var _a;
|
|
12
30
|
this.menus = [
|
|
31
|
+
{
|
|
32
|
+
name: 'KPI\nOversight',
|
|
33
|
+
path: 'kpi-overview',
|
|
34
|
+
icons: ICONS_STATUS,
|
|
35
|
+
submenus: [
|
|
36
|
+
{ name: 'KPI 대시보드', path: 'kpi-dashboard-map', icon: 'dashboard' },
|
|
37
|
+
{ name: 'KPI 개요', path: 'kpi-overview', icon: 'widget_small' },
|
|
38
|
+
{ name: 'KPI 시스템 가이드', path: 'kpi-system-guide', icon: 'menu_book' }
|
|
39
|
+
]
|
|
40
|
+
},
|
|
13
41
|
{
|
|
14
42
|
name: '진행중',
|
|
15
43
|
path: 'project-list',
|
|
@@ -30,26 +58,70 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
|
|
|
30
58
|
// path: 'project-task-list',
|
|
31
59
|
// icons: ICONS_OPERATING
|
|
32
60
|
// },
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
...(this.canMutate
|
|
62
|
+
? [
|
|
63
|
+
{
|
|
64
|
+
name: '셋팅',
|
|
65
|
+
path: 'project-setting-list',
|
|
66
|
+
icons: ICONS_SETTING
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
: [])
|
|
38
70
|
];
|
|
39
71
|
var page = this.page || '';
|
|
40
72
|
return html `
|
|
41
73
|
<ul>
|
|
42
|
-
${this.menus.map(menu =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
${this.menus.map((menu, i) => {
|
|
75
|
+
/* 서브메뉴가 있으면 이동 대신 팝업을 연다 (dkpi 방식) */
|
|
76
|
+
const active = menu.submenus
|
|
77
|
+
? menu.submenus.some(sub => !!~page.indexOf(sub.path))
|
|
78
|
+
: !!~page.indexOf(menu.path);
|
|
79
|
+
return html `
|
|
80
|
+
<li
|
|
81
|
+
@click=${menu.submenus
|
|
82
|
+
? (e) => {
|
|
83
|
+
e.stopPropagation();
|
|
84
|
+
this.openedMenuIndex = i;
|
|
85
|
+
}
|
|
86
|
+
: undefined}
|
|
87
|
+
>
|
|
88
|
+
<a href=${menu.submenus ? 'javascript:void(0)' : menu.path} ?active=${active}>
|
|
89
|
+
<img src=${active ? menu.icons[1] : menu.icons[0]} />
|
|
46
90
|
<div>${menu.name}</div>
|
|
47
91
|
</a>
|
|
48
92
|
</li>
|
|
49
|
-
|
|
93
|
+
`;
|
|
94
|
+
})}
|
|
50
95
|
</ul>
|
|
96
|
+
${this.openedMenuIndex !== null && ((_a = this.menus[this.openedMenuIndex]) === null || _a === void 0 ? void 0 : _a.submenus)
|
|
97
|
+
? html `
|
|
98
|
+
<div class="submenu-popup">
|
|
99
|
+
<ul>
|
|
100
|
+
${this.menus[this.openedMenuIndex].submenus.map(sub => html `
|
|
101
|
+
<li>
|
|
102
|
+
<a href=${sub.path} ?active=${!!~page.indexOf(sub.path)}>
|
|
103
|
+
<md-icon>${sub.icon}</md-icon> ${sub.name}
|
|
104
|
+
</a>
|
|
105
|
+
</li>
|
|
106
|
+
`)}
|
|
107
|
+
</ul>
|
|
108
|
+
</div>
|
|
109
|
+
`
|
|
110
|
+
: ''}
|
|
51
111
|
`;
|
|
52
112
|
}
|
|
113
|
+
connectedCallback() {
|
|
114
|
+
super.connectedCallback();
|
|
115
|
+
// capture 단계로 등록 — stopPropagation 하는 다른 컴포넌트의 클릭에도 서브메뉴를 닫는다 (dkpi 방식)
|
|
116
|
+
window.addEventListener('click', this._closeSubmenu, true);
|
|
117
|
+
canDcspMutate().then(granted => {
|
|
118
|
+
this.canMutate = granted;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
disconnectedCallback() {
|
|
122
|
+
window.removeEventListener('click', this._closeSubmenu, true);
|
|
123
|
+
super.disconnectedCallback();
|
|
124
|
+
}
|
|
53
125
|
stateChanged(state) {
|
|
54
126
|
this.page = state.route.page;
|
|
55
127
|
this.width = state.layout.width;
|
|
@@ -58,6 +130,7 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
|
|
|
58
130
|
MenuTools.styles = [
|
|
59
131
|
css `
|
|
60
132
|
:host {
|
|
133
|
+
position: relative;
|
|
61
134
|
display: flex;
|
|
62
135
|
background-color: var(--secondary-color);
|
|
63
136
|
|
|
@@ -75,7 +148,8 @@ MenuTools.styles = [
|
|
|
75
148
|
height: 100%;
|
|
76
149
|
}
|
|
77
150
|
|
|
78
|
-
ul
|
|
151
|
+
/* 사이드바 메뉴 스타일 — 팝업 내부로 새지 않도록 직계 자식(> ul)으로 스코핑 */
|
|
152
|
+
:host > ul {
|
|
79
153
|
display: flex;
|
|
80
154
|
flex-direction: row;
|
|
81
155
|
|
|
@@ -86,24 +160,24 @@ MenuTools.styles = [
|
|
|
86
160
|
overflow: none;
|
|
87
161
|
}
|
|
88
162
|
|
|
89
|
-
:host([width='NARROW']) ul {
|
|
163
|
+
:host([width='NARROW']) > ul {
|
|
90
164
|
width: 100%;
|
|
91
165
|
justify-content: space-around;
|
|
92
166
|
}
|
|
93
167
|
|
|
94
|
-
:host([width='WIDE']) ul {
|
|
168
|
+
:host([width='WIDE']) > ul {
|
|
95
169
|
flex-direction: column;
|
|
96
170
|
}
|
|
97
|
-
:host([width='NARROW']) li {
|
|
171
|
+
:host([width='NARROW']) > ul > li {
|
|
98
172
|
flex: 1;
|
|
99
173
|
}
|
|
100
174
|
|
|
101
|
-
:host([width='WIDE']) li {
|
|
175
|
+
:host([width='WIDE']) > ul > li {
|
|
102
176
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
103
177
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
104
178
|
}
|
|
105
179
|
|
|
106
|
-
a {
|
|
180
|
+
:host > ul a {
|
|
107
181
|
display: flex;
|
|
108
182
|
flex-direction: column;
|
|
109
183
|
padding: 5px 0px;
|
|
@@ -116,7 +190,7 @@ MenuTools.styles = [
|
|
|
116
190
|
border-left: 2px solid transparent;
|
|
117
191
|
}
|
|
118
192
|
|
|
119
|
-
a[active] {
|
|
193
|
+
:host > ul a[active] {
|
|
120
194
|
opacity: 1;
|
|
121
195
|
color: var(--menu-tools-active-color);
|
|
122
196
|
font-weight: bold;
|
|
@@ -124,7 +198,7 @@ MenuTools.styles = [
|
|
|
124
198
|
border-left: 2px solid var(--menu-tools-active-color);
|
|
125
199
|
}
|
|
126
200
|
|
|
127
|
-
:host([width='NARROW']) a {
|
|
201
|
+
:host([width='NARROW']) > ul a {
|
|
128
202
|
padding: 0px 0px 5px 0px;
|
|
129
203
|
opacity: 0.8;
|
|
130
204
|
color: var(--menu-tools-color);
|
|
@@ -132,7 +206,7 @@ MenuTools.styles = [
|
|
|
132
206
|
border-top: 2px solid transparent;
|
|
133
207
|
}
|
|
134
208
|
|
|
135
|
-
:host([width='NARROW']) a[active] {
|
|
209
|
+
:host([width='NARROW']) > ul a[active] {
|
|
136
210
|
opacity: 1;
|
|
137
211
|
color: var(--menu-tools-active-color);
|
|
138
212
|
font-weight: bold;
|
|
@@ -151,8 +225,73 @@ MenuTools.styles = [
|
|
|
151
225
|
padding: 0;
|
|
152
226
|
}
|
|
153
227
|
|
|
154
|
-
div {
|
|
228
|
+
:host > ul div {
|
|
155
229
|
font-size: 0.6em;
|
|
230
|
+
white-space: pre-line; /* 메뉴명 내 \n 줄바꿈 표현 */
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* dkpi menu-tools의 서브메뉴 팝업과 동일 구성 — 배경만 테마색(--secondary-color) 사용 */
|
|
234
|
+
.submenu-popup {
|
|
235
|
+
position: absolute;
|
|
236
|
+
left: 60px; /* 탑메뉴 width */
|
|
237
|
+
top: 0;
|
|
238
|
+
width: 220px;
|
|
239
|
+
height: 100%;
|
|
240
|
+
background: var(--secondary-color);
|
|
241
|
+
color: #fff;
|
|
242
|
+
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
|
243
|
+
z-index: 1000;
|
|
244
|
+
display: flex;
|
|
245
|
+
flex-direction: column;
|
|
246
|
+
border-radius: 0 10px 10px 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.submenu-popup ul {
|
|
250
|
+
list-style: none;
|
|
251
|
+
margin: 0;
|
|
252
|
+
padding: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.submenu-popup li {
|
|
256
|
+
/* em 은 상속 기준에 따라 커질 수 있어 고정 크기 사용 */
|
|
257
|
+
font-size: 14px;
|
|
258
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
259
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.submenu-popup li a {
|
|
263
|
+
color: var(--menu-tools-color);
|
|
264
|
+
opacity: 0.7;
|
|
265
|
+
text-decoration: none;
|
|
266
|
+
text-transform: capitalize;
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: row;
|
|
269
|
+
align-items: center;
|
|
270
|
+
gap: 12px;
|
|
271
|
+
transition: background 0.2s;
|
|
272
|
+
padding: 12px 16px;
|
|
273
|
+
border-left: 2px solid transparent;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.submenu-popup li a:hover {
|
|
277
|
+
background: rgba(0, 0, 0, 0.15);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.submenu-popup li a[active] {
|
|
281
|
+
opacity: 1;
|
|
282
|
+
color: var(--menu-tools-active-color);
|
|
283
|
+
font-weight: bold;
|
|
284
|
+
background-color: rgba(0, 0, 0, 0.15);
|
|
285
|
+
border-left: 2px solid var(--menu-tools-active-color);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.submenu-popup li a md-icon {
|
|
289
|
+
--md-icon-size: 20px;
|
|
290
|
+
color: rgba(255, 255, 255, 0.8);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.submenu-popup li a[active] md-icon {
|
|
294
|
+
color: var(--menu-tools-active-color);
|
|
156
295
|
}
|
|
157
296
|
`
|
|
158
297
|
];
|
|
@@ -164,6 +303,14 @@ __decorate([
|
|
|
164
303
|
property({ type: String, reflect: true }),
|
|
165
304
|
__metadata("design:type", String)
|
|
166
305
|
], MenuTools.prototype, "width", void 0);
|
|
306
|
+
__decorate([
|
|
307
|
+
property({ type: Number }),
|
|
308
|
+
__metadata("design:type", Object)
|
|
309
|
+
], MenuTools.prototype, "openedMenuIndex", void 0);
|
|
310
|
+
__decorate([
|
|
311
|
+
property({ type: Boolean }),
|
|
312
|
+
__metadata("design:type", Object)
|
|
313
|
+
], MenuTools.prototype, "canMutate", void 0);
|
|
167
314
|
MenuTools = __decorate([
|
|
168
315
|
customElement('menu-tools')
|
|
169
316
|
], MenuTools);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAS,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAiC,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAG5G,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC;IAAlD;;QA0GG,UAAK,GAAsD,EAAE,CAAA;IAqDvE,CAAC;IAnDC,MAAM;QACJ,IAAI,CAAC,KAAK,GAAG;YACX;gBACE,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,cAAc;gBACpB,KAAK,EAAE,cAAc;aACtB;YACD;gBACE,IAAI,EAAE,IAAI;gBACV,IAAI,EAAE,wBAAwB;gBAC9B,KAAK,EAAE,eAAe;aACvB;YACD,IAAI;YACJ,kBAAkB;YAClB,yBAAyB;YACzB,wBAAwB;YACxB,KAAK;YACL,IAAI;YACJ,iBAAiB;YACjB,+BAA+B;YAC/B,2BAA2B;YAC3B,KAAK;YACL;gBACE,IAAI,EAAE,IAAI;gBACV,IAAI,EAAE,sBAAsB;gBAC5B,KAAK,EAAE,aAAa;aACrB;SACF,CAAA;QAED,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;QAE1B,OAAO,IAAI,CAAA;;UAEL,IAAI,CAAC,KAAK,CAAC,GAAG,CACd,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA;;wBAEE,IAAI,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;2BAC5C,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;uBAC9D,IAAI,CAAC,IAAI;;;WAGrB,CACF;;KAEJ,CAAA;IACH,CAAC;IAED,YAAY,CAAC,KAAK;QAChB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAA;IACjC,CAAC;;AA7JM,gBAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkGF;CACF,AApGY,CAoGZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uCAAc;AACE;IAA1C,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCAAe;AAxG9C,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CA+JrB","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport { store, connect } from '@operato/shell'\n\nimport { ICONS_PROGRESS, ICONS_COMPLETED, ICONS_STATUS, ICONS_OPERATING, ICONS_SETTING } from '../icons/menu-icons'\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n background-color: var(--secondary-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n --menu-tools-color: rgba(255, 255, 255, 0.9);\n --menu-tools-active-color: rgba(107, 178, 249, 1);\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='NARROW']) ul {\n width: 100%;\n justify-content: space-around;\n }\n\n :host([width='WIDE']) ul {\n flex-direction: column;\n }\n :host([width='NARROW']) li {\n flex: 1;\n }\n\n :host([width='WIDE']) li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n a {\n display: flex;\n flex-direction: column;\n padding: 5px 0px;\n opacity: 0.7;\n align-items: center;\n text-align: center;\n text-decoration: none;\n text-transform: capitalize;\n color: var(--menu-tools-color);\n border-left: 2px solid transparent;\n }\n\n a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: 2px solid var(--menu-tools-active-color);\n }\n\n :host([width='NARROW']) a {\n padding: 0px 0px 5px 0px;\n opacity: 0.8;\n color: var(--menu-tools-color);\n border-left: none;\n border-top: 2px solid transparent;\n }\n\n :host([width='NARROW']) a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: none;\n border-top: 2px solid var(--menu-tools-active-color);\n }\n\n img {\n display: block;\n width: 35px;\n padding: 5px 10px 0px 10px;\n }\n\n :host([width='NARROW']) img {\n padding: 0;\n }\n\n div {\n font-size: 0.6em;\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n\n private menus: { name: string; path: string; icons: string[] }[] = []\n\n render() {\n this.menus = [\n {\n name: '진행중',\n path: 'project-list',\n icons: ICONS_PROGRESS\n },\n {\n name: '완료',\n path: 'project-completed-list',\n icons: ICONS_COMPLETED\n },\n // {\n // name: '검측현황',\n // path: 'dssp-status',\n // icons: ICONS_STATUS\n // },\n // {\n // name: '공정표',\n // path: 'project-task-list',\n // icons: ICONS_OPERATING\n // },\n {\n name: '셋팅',\n path: 'project-setting-list',\n icons: ICONS_SETTING\n }\n ]\n\n var page = this.page || ''\n\n return html`\n <ul>\n ${this.menus.map(\n menu => html`\n <li>\n <a href=${menu.path} ?active=${!!~page.indexOf(menu.path)}>\n <img src=${!!~page.indexOf(menu.path) ? menu.icons[1] : menu.icons[0]} />\n <div>${menu.name}</div>\n </a>\n </li>\n `\n )}\n </ul>\n `\n }\n\n stateChanged(state) {\n this.page = state.route.page\n this.width = state.layout.width\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport { store, connect } from '@operato/shell'\nimport { canDcspMutate } from '@dssp/building-complex/dist-client/utils/privilege'\n\nimport { ICONS_PROGRESS, ICONS_COMPLETED, ICONS_STATUS, ICONS_OPERATING, ICONS_SETTING } from '../icons/menu-icons'\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n position: relative;\n display: flex;\n background-color: var(--secondary-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n --menu-tools-color: rgba(255, 255, 255, 0.9);\n --menu-tools-active-color: rgba(107, 178, 249, 1);\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n /* 사이드바 메뉴 스타일 — 팝업 내부로 새지 않도록 직계 자식(> ul)으로 스코핑 */\n :host > ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='NARROW']) > ul {\n width: 100%;\n justify-content: space-around;\n }\n\n :host([width='WIDE']) > ul {\n flex-direction: column;\n }\n :host([width='NARROW']) > ul > li {\n flex: 1;\n }\n\n :host([width='WIDE']) > ul > li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n :host > ul a {\n display: flex;\n flex-direction: column;\n padding: 5px 0px;\n opacity: 0.7;\n align-items: center;\n text-align: center;\n text-decoration: none;\n text-transform: capitalize;\n color: var(--menu-tools-color);\n border-left: 2px solid transparent;\n }\n\n :host > ul a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: 2px solid var(--menu-tools-active-color);\n }\n\n :host([width='NARROW']) > ul a {\n padding: 0px 0px 5px 0px;\n opacity: 0.8;\n color: var(--menu-tools-color);\n border-left: none;\n border-top: 2px solid transparent;\n }\n\n :host([width='NARROW']) > ul a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: none;\n border-top: 2px solid var(--menu-tools-active-color);\n }\n\n img {\n display: block;\n width: 35px;\n padding: 5px 10px 0px 10px;\n }\n\n :host([width='NARROW']) img {\n padding: 0;\n }\n\n :host > ul div {\n font-size: 0.6em;\n white-space: pre-line; /* 메뉴명 내 \\n 줄바꿈 표현 */\n }\n\n /* dkpi menu-tools의 서브메뉴 팝업과 동일 구성 — 배경만 테마색(--secondary-color) 사용 */\n .submenu-popup {\n position: absolute;\n left: 60px; /* 탑메뉴 width */\n top: 0;\n width: 220px;\n height: 100%;\n background: var(--secondary-color);\n color: #fff;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n border-radius: 0 10px 10px 0;\n }\n\n .submenu-popup ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n .submenu-popup li {\n /* em 은 상속 기준에 따라 커질 수 있어 고정 크기 사용 */\n font-size: 14px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n .submenu-popup li a {\n color: var(--menu-tools-color);\n opacity: 0.7;\n text-decoration: none;\n text-transform: capitalize;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n transition: background 0.2s;\n padding: 12px 16px;\n border-left: 2px solid transparent;\n }\n\n .submenu-popup li a:hover {\n background: rgba(0, 0, 0, 0.15);\n }\n\n .submenu-popup li a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: 2px solid var(--menu-tools-active-color);\n }\n\n .submenu-popup li a md-icon {\n --md-icon-size: 20px;\n color: rgba(255, 255, 255, 0.8);\n }\n\n .submenu-popup li a[active] md-icon {\n color: var(--menu-tools-active-color);\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n @property({ type: Number }) openedMenuIndex: number | null = null\n /** 뮤테이션 가능 여부('dcsp guest' 권한 보유 시 false) — 게스트에게는 셋팅 메뉴를 숨긴다 */\n @property({ type: Boolean }) canMutate = false\n\n private menus: {\n name: string\n path: string\n icons: string[]\n submenus?: { name: string; path: string; icon: string }[]\n }[] = []\n\n render() {\n this.menus = [\n {\n name: 'KPI\\nOversight',\n path: 'kpi-overview',\n icons: ICONS_STATUS,\n submenus: [\n { name: 'KPI 대시보드', path: 'kpi-dashboard-map', icon: 'dashboard' },\n { name: 'KPI 개요', path: 'kpi-overview', icon: 'widget_small' },\n { name: 'KPI 시스템 가이드', path: 'kpi-system-guide', icon: 'menu_book' }\n ]\n },\n {\n name: '진행중',\n path: 'project-list',\n icons: ICONS_PROGRESS\n },\n {\n name: '완료',\n path: 'project-completed-list',\n icons: ICONS_COMPLETED\n },\n // {\n // name: '검측현황',\n // path: 'dssp-status',\n // icons: ICONS_STATUS\n // },\n // {\n // name: '공정표',\n // path: 'project-task-list',\n // icons: ICONS_OPERATING\n // },\n ...(this.canMutate\n ? [\n {\n name: '셋팅',\n path: 'project-setting-list',\n icons: ICONS_SETTING\n }\n ]\n : [])\n ]\n\n var page = this.page || ''\n\n return html`\n <ul>\n ${this.menus.map((menu, i) => {\n /* 서브메뉴가 있으면 이동 대신 팝업을 연다 (dkpi 방식) */\n const active = menu.submenus\n ? menu.submenus.some(sub => !!~page.indexOf(sub.path))\n : !!~page.indexOf(menu.path)\n\n return html`\n <li\n @click=${menu.submenus\n ? (e: Event) => {\n e.stopPropagation()\n this.openedMenuIndex = i\n }\n : undefined}\n >\n <a href=${menu.submenus ? 'javascript:void(0)' : menu.path} ?active=${active}>\n <img src=${active ? menu.icons[1] : menu.icons[0]} />\n <div>${menu.name}</div>\n </a>\n </li>\n `\n })}\n </ul>\n ${this.openedMenuIndex !== null && this.menus[this.openedMenuIndex]?.submenus\n ? html`\n <div class=\"submenu-popup\">\n <ul>\n ${this.menus[this.openedMenuIndex].submenus!.map(\n sub => html`\n <li>\n <a href=${sub.path} ?active=${!!~page.indexOf(sub.path)}>\n <md-icon>${sub.icon}</md-icon> ${sub.name}\n </a>\n </li>\n `\n )}\n </ul>\n </div>\n `\n : ''}\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n // capture 단계로 등록 — stopPropagation 하는 다른 컴포넌트의 클릭에도 서브메뉴를 닫는다 (dkpi 방식)\n window.addEventListener('click', this._closeSubmenu, true)\n\n canDcspMutate().then(granted => {\n this.canMutate = granted\n })\n }\n\n disconnectedCallback() {\n window.removeEventListener('click', this._closeSubmenu, true)\n super.disconnectedCallback()\n }\n\n private _closeSubmenu = (e: MouseEvent) => {\n const path = e.composedPath()\n if (path.includes(this)) {\n // 내부 클릭이라도 실제 페이지로 이동하는 링크 클릭이면 닫는다.\n const isNavLink = path.some(\n el =>\n el instanceof HTMLAnchorElement &&\n !!el.getAttribute('href') &&\n el.getAttribute('href') !== 'javascript:void(0)'\n )\n if (!isNavLink) return\n }\n this.openedMenuIndex = null\n }\n\n stateChanged(state) {\n this.page = state.route.page\n this.width = state.layout.width\n }\n}\n"]}
|
package/dist-server/index.d.ts
CHANGED
package/dist-server/index.js
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./middlewares/index.js"), exports);
|
|
1
6
|
//# sourceMappingURL=index.js.map
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,iEAAsC","sourcesContent":["export * from './service/index.js'\nexport * from './middlewares/index.js'\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 게스트 계정('dcsp guest' 권한 보유)의 무보호 GraphQL 뮤테이션을 차단하는 미들웨어를 등록하는 함수
|
|
3
|
+
* - 자체 @privilege 디렉티브가 있는 뮤테이션은 통과 (해당 디렉티브가 세분화된 권한을 검사)
|
|
4
|
+
* - 디렉티브가 없는 뮤테이션(dssp 패키지의 무보호 CUD)은 게스트만 차단, 그 외(기본)는 이전과 동일하게 허용
|
|
5
|
+
* - auth-base initMiddlewares 이후에 등록되므로 ctx.state.user / domain 이 설정된 상태로 실행됨
|
|
6
|
+
*/
|
|
7
|
+
export declare function initMiddlewares(app: any): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initMiddlewares = initMiddlewares;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const koa_bodyparser_1 = tslib_1.__importDefault(require("koa-bodyparser"));
|
|
6
|
+
const graphql_1 = require("graphql");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
/* 게스트 판정용 신규 권한 등록 — migration:privileges 실행 시 DB privilege 테이블에 insert 되어 역할 화면에 노출됨.
|
|
9
|
+
* 의미 반전형 권한: 이 권한이 "있으면" 게스트(제한). 기본(미부여) 상태는 이전과 완전히 동일하게 동작한다. */
|
|
10
|
+
process['PRIVILEGES'] = process['PRIVILEGES'] || {};
|
|
11
|
+
process['PRIVILEGES']['dcsp guest'] = ['dcsp', 'guest'];
|
|
12
|
+
/* 자체 @privilege 디렉티브가 없어도 게스트에게 허용하는 뮤테이션 (알림 읽음 처리/정리) */
|
|
13
|
+
const GUEST_ALLOWED_MUTATIONS = new Set([
|
|
14
|
+
'updateNotification',
|
|
15
|
+
'updateMultipleNotification',
|
|
16
|
+
'deleteNotification',
|
|
17
|
+
'deleteNotificationes'
|
|
18
|
+
]);
|
|
19
|
+
/*
|
|
20
|
+
* 자체 @privilege 디렉티브를 가진 뮤테이션 필드명 집합.
|
|
21
|
+
* 이 뮤테이션들은 자체 디렉티브가 권한을 검사하므로 전역 차단에서 제외한다.
|
|
22
|
+
* (예: updateUser는 'user mutation' 권한을 역할에 부여하면 게스트도 사용 가능)
|
|
23
|
+
*/
|
|
24
|
+
const SELF_PROTECTED_MUTATIONS = new Set();
|
|
25
|
+
/* 서버 기동 완료 시 빌드된 스키마에서 자체 보호 뮤테이션 목록을 수집하는 훅 */
|
|
26
|
+
process.on('bootstrap-module-start', ({ builtSchema }) => {
|
|
27
|
+
var _a, _b, _c, _d;
|
|
28
|
+
const fields = ((_a = builtSchema === null || builtSchema === void 0 ? void 0 : builtSchema.getMutationType()) === null || _a === void 0 ? void 0 : _a.getFields()) || {};
|
|
29
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
30
|
+
/* auth-base privilege-directive가 보호 필드의 description에 '🔒 Requires privilege' 마커를 남김 */
|
|
31
|
+
const hasDirective = ((_c = (_b = field.astNode) === null || _b === void 0 ? void 0 : _b.directives) === null || _c === void 0 ? void 0 : _c.some(directive => directive.name.value === 'privilege')) ||
|
|
32
|
+
((_d = field.description) === null || _d === void 0 ? void 0 : _d.includes('🔒 Requires privilege'));
|
|
33
|
+
if (hasDirective) {
|
|
34
|
+
SELF_PROTECTED_MUTATIONS.add(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
/* GraphQL 쿼리 문자열에서 mutation 최상위 필드명들을 추출하는 함수 */
|
|
39
|
+
function extractMutationFields(query) {
|
|
40
|
+
const document = (0, graphql_1.parse)(query);
|
|
41
|
+
return document.definitions
|
|
42
|
+
.filter((def) => def.kind === 'OperationDefinition')
|
|
43
|
+
.filter(def => def.operation === 'mutation')
|
|
44
|
+
.flatMap(def => def.selectionSet.selections
|
|
45
|
+
.filter((sel) => sel.kind === 'Field')
|
|
46
|
+
.map(sel => sel.name.value));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 게스트 계정('dcsp guest' 권한 보유)의 무보호 GraphQL 뮤테이션을 차단하는 미들웨어를 등록하는 함수
|
|
50
|
+
* - 자체 @privilege 디렉티브가 있는 뮤테이션은 통과 (해당 디렉티브가 세분화된 권한을 검사)
|
|
51
|
+
* - 디렉티브가 없는 뮤테이션(dssp 패키지의 무보호 CUD)은 게스트만 차단, 그 외(기본)는 이전과 동일하게 허용
|
|
52
|
+
* - auth-base initMiddlewares 이후에 등록되므로 ctx.state.user / domain 이 설정된 상태로 실행됨
|
|
53
|
+
*/
|
|
54
|
+
function initMiddlewares(app) {
|
|
55
|
+
const parseBody = (0, koa_bodyparser_1.default)();
|
|
56
|
+
app.use(async (ctx, next) => {
|
|
57
|
+
if (ctx.method !== 'POST' || !ctx.path.startsWith('/graphql')) {
|
|
58
|
+
return next();
|
|
59
|
+
}
|
|
60
|
+
/* 전역 bodyparser는 이 미들웨어 이후에 등록되지만, 이미 파싱된 body는 스킵하므로 먼저 파싱해도 안전함 */
|
|
61
|
+
await parseBody(ctx, async () => { });
|
|
62
|
+
const bodies = [].concat(ctx.request.body || []);
|
|
63
|
+
let mutationFields = [];
|
|
64
|
+
try {
|
|
65
|
+
for (const body of bodies) {
|
|
66
|
+
if (body === null || body === void 0 ? void 0 : body.query) {
|
|
67
|
+
mutationFields = mutationFields.concat(extractMutationFields(body.query));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
/* 쿼리 파싱 실패 시 Apollo 핸들러가 에러를 처리하도록 통과 */
|
|
73
|
+
return next();
|
|
74
|
+
}
|
|
75
|
+
/* 게스트 허용 목록과 자체 보호 뮤테이션을 제외한 무보호 뮤테이션만 차단 대상 */
|
|
76
|
+
const blocked = mutationFields.filter(name => !GUEST_ALLOWED_MUTATIONS.has(name) && !SELF_PROTECTED_MUTATIONS.has(name));
|
|
77
|
+
if (blocked.length === 0) {
|
|
78
|
+
return next();
|
|
79
|
+
}
|
|
80
|
+
const { user, domain } = ctx.state;
|
|
81
|
+
/* 'dcsp guest' 권한을 가진 역할의 사용자만 게스트로 차단.
|
|
82
|
+
* checkPermission의 owner/super 특례는 의미가 반대로 작용하므로 순수 조회인 User.hasPrivilege 를 사용한다.
|
|
83
|
+
* (미인증 요청은 auth 미들웨어가 먼저 401 처리하므로 user 없음 = 통과로 둬도 안전) */
|
|
84
|
+
const isGuest = user && domain && (await auth_base_1.User.hasPrivilege('guest', 'dcsp', domain, user));
|
|
85
|
+
if (!isGuest) {
|
|
86
|
+
return next();
|
|
87
|
+
}
|
|
88
|
+
/* Apollo 형식 에러로 응답하여 클라이언트의 기존 GraphQL 에러 표시를 그대로 활용 */
|
|
89
|
+
ctx.status = 200;
|
|
90
|
+
ctx.body = {
|
|
91
|
+
errors: [
|
|
92
|
+
{
|
|
93
|
+
message: '권한이 없습니다. 관리자에게 문의하세요.',
|
|
94
|
+
extensions: { code: 'FORBIDDEN' }
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":"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","sourcesContent":["import bodyParser from 'koa-bodyparser'\nimport { parse, OperationDefinitionNode, FieldNode, GraphQLSchema } from 'graphql'\n\nimport { User } from '@things-factory/auth-base'\n\n/* 게스트 판정용 신규 권한 등록 — migration:privileges 실행 시 DB privilege 테이블에 insert 되어 역할 화면에 노출됨.\n * 의미 반전형 권한: 이 권한이 \"있으면\" 게스트(제한). 기본(미부여) 상태는 이전과 완전히 동일하게 동작한다. */\nprocess['PRIVILEGES'] = process['PRIVILEGES'] || {}\nprocess['PRIVILEGES']['dcsp guest'] = ['dcsp', 'guest']\n\n/* 자체 @privilege 디렉티브가 없어도 게스트에게 허용하는 뮤테이션 (알림 읽음 처리/정리) */\nconst GUEST_ALLOWED_MUTATIONS = new Set([\n 'updateNotification',\n 'updateMultipleNotification',\n 'deleteNotification',\n 'deleteNotificationes'\n])\n\n/*\n * 자체 @privilege 디렉티브를 가진 뮤테이션 필드명 집합.\n * 이 뮤테이션들은 자체 디렉티브가 권한을 검사하므로 전역 차단에서 제외한다.\n * (예: updateUser는 'user mutation' 권한을 역할에 부여하면 게스트도 사용 가능)\n */\nconst SELF_PROTECTED_MUTATIONS = new Set<string>()\n\n/* 서버 기동 완료 시 빌드된 스키마에서 자체 보호 뮤테이션 목록을 수집하는 훅 */\nprocess.on('bootstrap-module-start' as any, ({ builtSchema }: { builtSchema: GraphQLSchema }) => {\n const fields = builtSchema?.getMutationType()?.getFields() || {}\n for (const [name, field] of Object.entries(fields)) {\n /* auth-base privilege-directive가 보호 필드의 description에 '🔒 Requires privilege' 마커를 남김 */\n const hasDirective =\n field.astNode?.directives?.some(directive => directive.name.value === 'privilege') ||\n field.description?.includes('🔒 Requires privilege')\n if (hasDirective) {\n SELF_PROTECTED_MUTATIONS.add(name)\n }\n }\n})\n\n/* GraphQL 쿼리 문자열에서 mutation 최상위 필드명들을 추출하는 함수 */\nfunction extractMutationFields(query: string): string[] {\n const document = parse(query)\n return document.definitions\n .filter((def): def is OperationDefinitionNode => def.kind === 'OperationDefinition')\n .filter(def => def.operation === 'mutation')\n .flatMap(def =>\n def.selectionSet.selections\n .filter((sel): sel is FieldNode => sel.kind === 'Field')\n .map(sel => sel.name.value)\n )\n}\n\n/**\n * 게스트 계정('dcsp guest' 권한 보유)의 무보호 GraphQL 뮤테이션을 차단하는 미들웨어를 등록하는 함수\n * - 자체 @privilege 디렉티브가 있는 뮤테이션은 통과 (해당 디렉티브가 세분화된 권한을 검사)\n * - 디렉티브가 없는 뮤테이션(dssp 패키지의 무보호 CUD)은 게스트만 차단, 그 외(기본)는 이전과 동일하게 허용\n * - auth-base initMiddlewares 이후에 등록되므로 ctx.state.user / domain 이 설정된 상태로 실행됨\n */\nexport function initMiddlewares(app) {\n const parseBody = bodyParser()\n\n app.use(async (ctx, next) => {\n if (ctx.method !== 'POST' || !ctx.path.startsWith('/graphql')) {\n return next()\n }\n\n /* 전역 bodyparser는 이 미들웨어 이후에 등록되지만, 이미 파싱된 body는 스킵하므로 먼저 파싱해도 안전함 */\n await parseBody(ctx, async () => {})\n\n const bodies = [].concat(ctx.request.body || [])\n let mutationFields: string[] = []\n\n try {\n for (const body of bodies) {\n if (body?.query) {\n mutationFields = mutationFields.concat(extractMutationFields(body.query))\n }\n }\n } catch (e) {\n /* 쿼리 파싱 실패 시 Apollo 핸들러가 에러를 처리하도록 통과 */\n return next()\n }\n\n /* 게스트 허용 목록과 자체 보호 뮤테이션을 제외한 무보호 뮤테이션만 차단 대상 */\n const blocked = mutationFields.filter(\n name => !GUEST_ALLOWED_MUTATIONS.has(name) && !SELF_PROTECTED_MUTATIONS.has(name)\n )\n if (blocked.length === 0) {\n return next()\n }\n\n const { user, domain } = ctx.state\n\n /* 'dcsp guest' 권한을 가진 역할의 사용자만 게스트로 차단.\n * checkPermission의 owner/super 특례는 의미가 반대로 작용하므로 순수 조회인 User.hasPrivilege 를 사용한다.\n * (미인증 요청은 auth 미들웨어가 먼저 401 처리하므로 user 없음 = 통과로 둬도 안전) */\n const isGuest = user && domain && (await User.hasPrivilege('guest', 'dcsp', domain, user))\n if (!isGuest) {\n return next()\n }\n\n /* Apollo 형식 에러로 응답하여 클라이언트의 기존 GraphQL 에러 표시를 그대로 활용 */\n ctx.status = 200\n ctx.body = {\n errors: [\n {\n message: '권한이 없습니다. 관리자에게 문의하세요.',\n extensions: { code: 'FORBIDDEN' }\n }\n ]\n }\n })\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const entities: any[];
|
|
2
|
+
export declare const schema: {
|
|
3
|
+
resolverClasses: (typeof import("./kpi-stat/kpi-stat-query").KpiStatQuery | typeof import("./kpi-value/kpi-value-query").KpiValueQueryForProject | typeof import("./kpi-metric-value/kpi-metric-value-query").KpiMetricValueQuery | typeof import("./kpi-metric-value/kpi-metric-value-mutation").KpiMetricValueMutation)[];
|
|
4
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.schema = exports.entities = void 0;
|
|
4
|
+
/* IMPORT ENTITIES AND RESOLVERS */
|
|
5
|
+
const kpi_stat_1 = require("./kpi-stat");
|
|
6
|
+
const kpi_value_1 = require("./kpi-value");
|
|
7
|
+
const kpi_metric_value_1 = require("./kpi-metric-value");
|
|
8
|
+
exports.entities = [
|
|
9
|
+
/* ENTITIES */
|
|
10
|
+
];
|
|
11
|
+
exports.schema = {
|
|
12
|
+
resolverClasses: [
|
|
13
|
+
/* RESOLVER CLASSES */
|
|
14
|
+
...kpi_stat_1.resolvers,
|
|
15
|
+
...kpi_value_1.resolvers,
|
|
16
|
+
...kpi_metric_value_1.resolvers
|
|
17
|
+
]
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;AAAA,mCAAmC;AACnC,yCAA0D;AAC1D,2CAA4D;AAC5D,yDAAyE;AAE5D,QAAA,QAAQ,GAAG;AACtB,cAAc;CACf,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAAgB;QACnB,GAAG,qBAAiB;QACpB,GAAG,4BAAuB;KAC3B;CACF,CAAA","sourcesContent":["/* IMPORT ENTITIES AND RESOLVERS */\nimport { resolvers as KpiStatResolvers } from './kpi-stat'\nimport { resolvers as KpiValueResolvers } from './kpi-value'\nimport { resolvers as KpiMetricValueResolvers } from './kpi-metric-value'\n\nexport const entities = [\n /* ENTITIES */\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...KpiStatResolvers,\n ...KpiValueResolvers,\n ...KpiMetricValueResolvers\n ]\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { KpiMetricValueQuery } from './kpi-metric-value-query';
|
|
2
|
+
import { KpiMetricValueMutation } from './kpi-metric-value-mutation';
|
|
3
|
+
export declare const resolvers: (typeof KpiMetricValueQuery | typeof KpiMetricValueMutation)[];
|
|
4
|
+
export declare const subscribers: any[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subscribers = exports.resolvers = void 0;
|
|
4
|
+
const kpi_metric_value_query_1 = require("./kpi-metric-value-query");
|
|
5
|
+
const kpi_metric_value_mutation_1 = require("./kpi-metric-value-mutation");
|
|
6
|
+
exports.resolvers = [kpi_metric_value_query_1.KpiMetricValueQuery, kpi_metric_value_mutation_1.KpiMetricValueMutation];
|
|
7
|
+
exports.subscribers = [];
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/kpi-metric-value/index.ts"],"names":[],"mappings":";;;AAAA,qEAA8D;AAC9D,2EAAoE;AAEvD,QAAA,SAAS,GAAG,CAAC,4CAAmB,EAAE,kDAAsB,CAAC,CAAA;AACzD,QAAA,WAAW,GAAG,EAAE,CAAA","sourcesContent":["import { KpiMetricValueQuery } from './kpi-metric-value-query'\nimport { KpiMetricValueMutation } from './kpi-metric-value-mutation'\n\nexport const resolvers = [KpiMetricValueQuery, KpiMetricValueMutation]\nexport const subscribers = []\n"]}
|