@things-factory/auth-ui 10.1.44 → 10.1.47
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 +3 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/auth-badge.d.ts +20 -0
- package/dist-client/components/auth-badge.js +50 -0
- package/dist-client/components/auth-badge.js.map +1 -0
- package/dist-client/components/auth-badge.test.d.ts +1 -0
- package/dist-client/components/auth-badge.test.js +37 -0
- package/dist-client/components/auth-badge.test.js.map +1 -0
- package/dist-client/components/create-domain-popup-theme.test.d.ts +1 -0
- package/dist-client/components/create-domain-popup-theme.test.js +16 -0
- package/dist-client/components/create-domain-popup-theme.test.js.map +1 -0
- package/dist-client/components/create-domain-popup.js +3 -1
- package/dist-client/components/create-domain-popup.js.map +1 -1
- package/dist-client/entity360/person-360.d.ts +22 -0
- package/dist-client/entity360/person-360.js +98 -0
- package/dist-client/entity360/person-360.js.map +1 -0
- package/dist-client/pages/role/role-list-page.d.ts +8 -0
- package/dist-client/pages/role/role-list-page.js +73 -24
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/pages/role/role-template-gap.test.d.ts +1 -0
- package/dist-client/pages/role/role-template-gap.test.js +30 -0
- package/dist-client/pages/role/role-template-gap.test.js.map +1 -0
- package/dist-client/pages/user/user-list-page.d.ts +1 -0
- package/dist-client/pages/user/user-list-page.js +6 -21
- package/dist-client/pages/user/user-list-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.p360.tsbuildinfo +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/tests/person-360-sections.test.ts +85 -0
- package/translations/en.json +4 -15
- package/translations/ja.json +73 -13
- package/translations/ko.json +4 -14
- package/translations/ms.json +75 -16
- package/translations/zh.json +73 -14
package/dist-client/bootstrap.js
CHANGED
|
@@ -9,7 +9,10 @@ import { OxGristEditorPrivilege } from '@operato/app/grist-editor/ox-grist-edito
|
|
|
9
9
|
import { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer, OxGristRendererJson5 } from '@operato/data-grist';
|
|
10
10
|
import { auth } from '@things-factory/auth-base/dist-client/auth.js';
|
|
11
11
|
import { ADD_MORENDA } from '@things-factory/more-base/client';
|
|
12
|
+
import { registerPersonKind } from './entity360/person-360.js';
|
|
12
13
|
export default async function bootstrap() {
|
|
14
|
+
/* the person 360 - a User value anywhere opens it (ADR-0067) */
|
|
15
|
+
registerPersonKind();
|
|
13
16
|
registerGristEditor('privilege', OxGristEditorPrivilege);
|
|
14
17
|
registerGristRenderer('privilege', OxGristRendererJson5);
|
|
15
18
|
/* add user profile morenda */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,iCAAiC,CAAA,CAAC,sDAAsD;AAE/F,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAE/B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAA;AAC/F,OAAO,EACL,cAAc,IAAI,mBAAmB,EACrC,gBAAgB,IAAI,qBAAqB,EACzC,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAE9D,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS;IACrC,mBAAmB,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAA;IACxD,qBAAqB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAA;IAExD,8BAA8B;IAC9B,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAA,qCAAqC;YAC/C,IAAI,EAAE,IAAI,CAAA,iDAAiD;YAC3D,QAAQ,EAAE,aAAa,CAAC,SAAS;YACjC,MAAM,EAAE,GAAG,EAAE;gBACX,QAAQ,CAAC,SAAS,CAAC,CAAA;YACrB,CAAC;SACF;KACF,CAAC,CAAA;IAEF,0BAA0B;IAC1B,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAA,kCAAkC;YAC5C,IAAI,EAAE,IAAI,CAAA,8CAA8C;YACxD,QAAQ,EAAE,aAAa,CAAC,SAAS;YACjC,MAAM,EAAE,GAAG,EAAE;gBACX,IAAI,CAAC;oBACH,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;wBACxC,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;wBACrC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;oBACpC,CAAC;oBAED,CAAC;oBAAA,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;wBACxC,IAAI,CAAC;4BACH,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,mDAAmD,CAAA;wBAC9E,CAAC;wBAAC,OAAO,CAAC,EAAE,CAAC;4BACX,SAAS;wBACX,CAAC;oBACH,CAAC,CAAC,CAAA;gBACJ,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,SAAS;gBACX,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAA;YAChB,CAAC;SACF;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;QACpC,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACpC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF;;;;MAIE;IACF,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;AACtB,CAAC","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\nimport { i18next } from '@operato/i18n'\nimport '@operato/attribute/grist-editor' /* for register data-grist editor type 'attributes' */\n\nimport { html } from 'lit-html'\n\nimport { navigate, store } from '@operato/shell'\nimport { TOOL_POSITION } from '@operato/layout'\nimport { OxGristEditorPrivilege } from '@operato/app/grist-editor/ox-grist-editor-privilege.js'\nimport {\n registerEditor as registerGristEditor,\n registerRenderer as registerGristRenderer,\n OxGristRendererJson5\n} from '@operato/data-grist'\n\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { ADD_MORENDA } from '@things-factory/more-base/client'\n\nexport default async function bootstrap() {\n registerGristEditor('privilege', OxGristEditorPrivilege)\n registerGristRenderer('privilege', OxGristRendererJson5)\n\n /* add user profile morenda */\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>account_circle</md-icon> `,\n name: html` <ox-i18n msgid=\"text.auth profile\"></ox-i18n> `,\n position: TOOL_POSITION.FRONT_END,\n action: () => {\n navigate('profile')\n }\n }\n })\n\n /* add sign-out morenda */\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>exit_to_app</md-icon> `,\n name: html` <ox-i18n msgid=\"field.sign out\"></ox-i18n> `,\n position: TOOL_POSITION.FRONT_END,\n action: () => {\n try {\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem('i18nextLng')\n localStorage.removeItem('i18next')\n }\n\n ;['i18next', 'i18nextLng'].forEach(name => {\n try {\n document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`\n } catch (e) {\n // ignore\n }\n })\n } catch (e) {\n // ignore\n }\n\n auth.signout()\n }\n }\n })\n\n auth.on('profile', ({ credential }) => {\n if (credential && credential.locale) {\n i18next.changeLanguage(credential.locale)\n }\n })\n\n /* \n Get user profile information from server. \n As soon as response received, auth.on('profile', ...) handlers start to work.\n It's very important point to build UI for the user.\n */\n await auth.profile()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,iCAAiC,CAAA,CAAC,sDAAsD;AAE/F,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAE/B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAA;AAC/F,OAAO,EACL,cAAc,IAAI,mBAAmB,EACrC,gBAAgB,IAAI,qBAAqB,EACzC,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAE9D,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS;IACrC,gEAAgE;IAChE,kBAAkB,EAAE,CAAA;IAEpB,mBAAmB,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAA;IACxD,qBAAqB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAA;IAExD,8BAA8B;IAC9B,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAA,qCAAqC;YAC/C,IAAI,EAAE,IAAI,CAAA,iDAAiD;YAC3D,QAAQ,EAAE,aAAa,CAAC,SAAS;YACjC,MAAM,EAAE,GAAG,EAAE;gBACX,QAAQ,CAAC,SAAS,CAAC,CAAA;YACrB,CAAC;SACF;KACF,CAAC,CAAA;IAEF,0BAA0B;IAC1B,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAA,kCAAkC;YAC5C,IAAI,EAAE,IAAI,CAAA,8CAA8C;YACxD,QAAQ,EAAE,aAAa,CAAC,SAAS;YACjC,MAAM,EAAE,GAAG,EAAE;gBACX,IAAI,CAAC;oBACH,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;wBACxC,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;wBACrC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;oBACpC,CAAC;oBAED,CAAC;oBAAA,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;wBACxC,IAAI,CAAC;4BACH,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,mDAAmD,CAAA;wBAC9E,CAAC;wBAAC,OAAO,CAAC,EAAE,CAAC;4BACX,SAAS;wBACX,CAAC;oBACH,CAAC,CAAC,CAAA;gBACJ,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,SAAS;gBACX,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAA;YAChB,CAAC;SACF;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;QACpC,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACpC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF;;;;MAIE;IACF,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;AACtB,CAAC","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\nimport { i18next } from '@operato/i18n'\nimport '@operato/attribute/grist-editor' /* for register data-grist editor type 'attributes' */\n\nimport { html } from 'lit-html'\n\nimport { navigate, store } from '@operato/shell'\nimport { TOOL_POSITION } from '@operato/layout'\nimport { OxGristEditorPrivilege } from '@operato/app/grist-editor/ox-grist-editor-privilege.js'\nimport {\n registerEditor as registerGristEditor,\n registerRenderer as registerGristRenderer,\n OxGristRendererJson5\n} from '@operato/data-grist'\n\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { ADD_MORENDA } from '@things-factory/more-base/client'\nimport { registerPersonKind } from './entity360/person-360.js'\n\nexport default async function bootstrap() {\n /* the person 360 - a User value anywhere opens it (ADR-0067) */\n registerPersonKind()\n\n registerGristEditor('privilege', OxGristEditorPrivilege)\n registerGristRenderer('privilege', OxGristRendererJson5)\n\n /* add user profile morenda */\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>account_circle</md-icon> `,\n name: html` <ox-i18n msgid=\"text.auth profile\"></ox-i18n> `,\n position: TOOL_POSITION.FRONT_END,\n action: () => {\n navigate('profile')\n }\n }\n })\n\n /* add sign-out morenda */\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>exit_to_app</md-icon> `,\n name: html` <ox-i18n msgid=\"field.sign out\"></ox-i18n> `,\n position: TOOL_POSITION.FRONT_END,\n action: () => {\n try {\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem('i18nextLng')\n localStorage.removeItem('i18next')\n }\n\n ;['i18next', 'i18nextLng'].forEach(name => {\n try {\n document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`\n } catch (e) {\n // ignore\n }\n })\n } catch (e) {\n // ignore\n }\n\n auth.signout()\n }\n }\n })\n\n auth.on('profile', ({ credential }) => {\n if (credential && credential.locale) {\n i18next.changeLanguage(credential.locale)\n }\n })\n\n /* \n Get user profile information from server. \n As soon as response received, auth.on('profile', ...) handlers start to work.\n It's very important point to build UI for the user.\n */\n await auth.profile()\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* A summary badge that carries its own look - for grid cells.
|
|
4
|
+
*
|
|
5
|
+
* The role and user lists drew their badges as `<span class="badge">` and styled `.badge` in the
|
|
6
|
+
* page. A grid cell renders inside the grid field's own shadow root (`ox-grid-field` ·
|
|
7
|
+
* `ox-list-field`), where page CSS does not reach, so every badge came out as plain text
|
|
8
|
+
* [확인함 2026-09-24 :3500 role list: background transparent, radius 0, padding 0]. The two that
|
|
9
|
+
* mattered most were the ones that said something waits on the reader - "grants nothing" and a
|
|
10
|
+
* locked account - and they looked like any other word in the row.
|
|
11
|
+
*
|
|
12
|
+
* Colour only where something waits on the reader (`attention`); the default state is quiet
|
|
13
|
+
* (`quiet`). Two lists in one package use it, so it lives here.
|
|
14
|
+
*/
|
|
15
|
+
export declare class AuthBadge extends LitElement {
|
|
16
|
+
static styles: import("lit").CSSResult;
|
|
17
|
+
/** `attention` when something waits on the reader, `quiet` for the default state, none otherwise. */
|
|
18
|
+
tone?: 'attention' | 'quiet';
|
|
19
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { LitElement, css, html } from 'lit';
|
|
3
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
+
/**
|
|
5
|
+
* A summary badge that carries its own look - for grid cells.
|
|
6
|
+
*
|
|
7
|
+
* The role and user lists drew their badges as `<span class="badge">` and styled `.badge` in the
|
|
8
|
+
* page. A grid cell renders inside the grid field's own shadow root (`ox-grid-field` ·
|
|
9
|
+
* `ox-list-field`), where page CSS does not reach, so every badge came out as plain text
|
|
10
|
+
* [확인함 2026-09-24 :3500 role list: background transparent, radius 0, padding 0]. The two that
|
|
11
|
+
* mattered most were the ones that said something waits on the reader - "grants nothing" and a
|
|
12
|
+
* locked account - and they looked like any other word in the row.
|
|
13
|
+
*
|
|
14
|
+
* Colour only where something waits on the reader (`attention`); the default state is quiet
|
|
15
|
+
* (`quiet`). Two lists in one package use it, so it lives here.
|
|
16
|
+
*/
|
|
17
|
+
let AuthBadge = class AuthBadge extends LitElement {
|
|
18
|
+
static { this.styles = css `
|
|
19
|
+
:host {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
padding: 2px 8px;
|
|
22
|
+
border-radius: var(--md-sys-shape-corner-small, 6px);
|
|
23
|
+
font-size: 0.78rem;
|
|
24
|
+
line-height: 1.4;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
background-color: var(--md-sys-color-surface-variant);
|
|
27
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
28
|
+
}
|
|
29
|
+
:host([tone='attention']) {
|
|
30
|
+
background-color: var(--md-sys-color-error-container);
|
|
31
|
+
color: var(--md-sys-color-on-error-container);
|
|
32
|
+
}
|
|
33
|
+
:host([tone='quiet']) {
|
|
34
|
+
background-color: transparent;
|
|
35
|
+
color: var(--md-sys-color-outline);
|
|
36
|
+
}
|
|
37
|
+
`; }
|
|
38
|
+
render() {
|
|
39
|
+
return html `<slot></slot>`;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
__decorate([
|
|
43
|
+
property({ reflect: true }),
|
|
44
|
+
__metadata("design:type", String)
|
|
45
|
+
], AuthBadge.prototype, "tone", void 0);
|
|
46
|
+
AuthBadge = __decorate([
|
|
47
|
+
customElement('auth-badge')
|
|
48
|
+
], AuthBadge);
|
|
49
|
+
export { AuthBadge };
|
|
50
|
+
//# sourceMappingURL=auth-badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-badge.js","sourceRoot":"","sources":["../../client/components/auth-badge.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D;;;;;;;;;;;;GAYG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;aAChC,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;GAmBlB,AAnBY,CAmBZ;IAKD,MAAM;QACJ,OAAO,IAAI,CAAA,eAAe,CAAA;IAC5B,CAAC;;AAJ4B;IAA5B,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;uCAA6B;AAvB9C,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CA4BrB","sourcesContent":["import { LitElement, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\n/**\n * A summary badge that carries its own look - for grid cells.\n *\n * The role and user lists drew their badges as `<span class=\"badge\">` and styled `.badge` in the\n * page. A grid cell renders inside the grid field's own shadow root (`ox-grid-field` ·\n * `ox-list-field`), where page CSS does not reach, so every badge came out as plain text\n * [확인함 2026-09-24 :3500 role list: background transparent, radius 0, padding 0]. The two that\n * mattered most were the ones that said something waits on the reader - \"grants nothing\" and a\n * locked account - and they looked like any other word in the row.\n *\n * Colour only where something waits on the reader (`attention`); the default state is quiet\n * (`quiet`). Two lists in one package use it, so it lives here.\n */\n@customElement('auth-badge')\nexport class AuthBadge extends LitElement {\n static styles = css`\n :host {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n line-height: 1.4;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n :host([tone='attention']) {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n :host([tone='quiet']) {\n background-color: transparent;\n color: var(--md-sys-color-outline);\n }\n `\n\n /** `attention` when something waits on the reader, `quiet` for the default state, none otherwise. */\n @property({ reflect: true }) tone?: 'attention' | 'quiet'\n\n render() {\n return html`<slot></slot>`\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Badges in grid cells carry their own look.
|
|
5
|
+
*
|
|
6
|
+
* A grid cell renders inside the grid field's shadow root, where page CSS does not reach. The role
|
|
7
|
+
* and user lists drew `<span class="badge">` and styled `.badge` in the page, so every badge came out
|
|
8
|
+
* as plain text - "grants nothing" and a locked account looked like any other word
|
|
9
|
+
* [확인함 2026-09-24 :3500]. `<auth-badge>` styles itself.
|
|
10
|
+
*/
|
|
11
|
+
const CLIENT = path.resolve(__dirname, '..');
|
|
12
|
+
function sources(dir, found = []) {
|
|
13
|
+
for (const entry of readdirSync(dir)) {
|
|
14
|
+
const full = path.join(dir, entry);
|
|
15
|
+
if (statSync(full).isDirectory())
|
|
16
|
+
sources(full, found);
|
|
17
|
+
else if (full.endsWith('.ts') && !full.endsWith('.test.ts'))
|
|
18
|
+
found.push(full);
|
|
19
|
+
}
|
|
20
|
+
return found;
|
|
21
|
+
}
|
|
22
|
+
/** Code only - a comment that quotes the old markup is not a use of it. */
|
|
23
|
+
const withoutComments = (text) => text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
24
|
+
describe('badges in auth-ui', () => {
|
|
25
|
+
it('★ no screen draws a page-styled badge span that a grid cell would leave unstyled', () => {
|
|
26
|
+
const offenders = sources(CLIENT)
|
|
27
|
+
.filter(file => /class="badge"/.test(withoutComments(readFileSync(file, 'utf-8'))))
|
|
28
|
+
.map(file => path.relative(CLIENT, file));
|
|
29
|
+
expect(offenders).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
it.each(['pages/role/role-list-page.ts', 'pages/user/user-list-page.ts'])('★ %s loads the badge it draws', file => {
|
|
32
|
+
const source = readFileSync(path.join(CLIENT, file), 'utf-8');
|
|
33
|
+
expect(source).toMatch(/<auth-badge/);
|
|
34
|
+
expect(source).toContain("import '../../components/auth-badge.js'");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=auth-badge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-badge.test.js","sourceRoot":"","sources":["../../client/components/auth-badge.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACxD,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB;;;;;;;GAOG;AAEH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;AAE5C,SAAS,OAAO,CAAC,GAAW,EAAE,QAAkB,EAAE;IAChD,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAClC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;aACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/E,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;AAE5G,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;aAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;aAClF,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;QAC3C,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC/B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,8BAA8B,CAAC,CAAC,CAAC,+BAA+B,EAAE,IAAI,CAAC,EAAE;QAChH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;QAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,yCAAyC,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { readdirSync, readFileSync, statSync } from 'fs'\nimport path from 'path'\n\n/**\n * Badges in grid cells carry their own look.\n *\n * A grid cell renders inside the grid field's shadow root, where page CSS does not reach. The role\n * and user lists drew `<span class=\"badge\">` and styled `.badge` in the page, so every badge came out\n * as plain text - \"grants nothing\" and a locked account looked like any other word\n * [확인함 2026-09-24 :3500]. `<auth-badge>` styles itself.\n */\n\nconst CLIENT = path.resolve(__dirname, '..')\n\nfunction sources(dir: string, found: string[] = []): string[] {\n for (const entry of readdirSync(dir)) {\n const full = path.join(dir, entry)\n if (statSync(full).isDirectory()) sources(full, found)\n else if (full.endsWith('.ts') && !full.endsWith('.test.ts')) found.push(full)\n }\n return found\n}\n\n/** Code only - a comment that quotes the old markup is not a use of it. */\nconst withoutComments = (text: string) => text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/^\\s*\\/\\/.*$/gm, '')\n\ndescribe('badges in auth-ui', () => {\n it('★ no screen draws a page-styled badge span that a grid cell would leave unstyled', () => {\n const offenders = sources(CLIENT)\n .filter(file => /class=\"badge\"/.test(withoutComments(readFileSync(file, 'utf-8'))))\n .map(file => path.relative(CLIENT, file))\n expect(offenders).toEqual([])\n })\n\n it.each(['pages/role/role-list-page.ts', 'pages/user/user-list-page.ts'])('★ %s loads the badge it draws', file => {\n const source = readFileSync(path.join(CLIENT, file), 'utf-8')\n expect(source).toMatch(/<auth-badge/)\n expect(source).toContain(\"import '../../components/auth-badge.js'\")\n })\n})\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* The create-domain popup marks an invalid name in the theme's error colours. It used a fixed
|
|
5
|
+
* #fce6e6 behind text that follows the theme, which in the dark theme was light text on pink.
|
|
6
|
+
*/
|
|
7
|
+
const source = readFileSync(path.join(__dirname, 'create-domain-popup.ts'), 'utf-8');
|
|
8
|
+
describe('the invalid-name mark in create-domain-popup', () => {
|
|
9
|
+
it('★ uses the error container pair, not a fixed colour', () => {
|
|
10
|
+
const rule = source.slice(source.indexOf('input.checkValidName {'), source.indexOf('}', source.indexOf('input.checkValidName {')));
|
|
11
|
+
expect(rule).toMatch(/background-color:\s*var\(--md-sys-color-error-container\)/);
|
|
12
|
+
expect(rule).toMatch(/color:\s*var\(--md-sys-color-on-error-container\)/);
|
|
13
|
+
expect(rule).not.toMatch(/#[0-9a-f]{3,6}/i);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=create-domain-popup-theme.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-domain-popup-theme.test.js","sourceRoot":"","sources":["../../client/components/create-domain-popup-theme.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB;;;GAGG;AACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,EAAE,OAAO,CAAC,CAAA;AAEpF,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC5D,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAA;QAClI,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAA;QACjF,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAA;QACzE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { readFileSync } from 'fs'\nimport path from 'path'\n\n/**\n * The create-domain popup marks an invalid name in the theme's error colours. It used a fixed\n * #fce6e6 behind text that follows the theme, which in the dark theme was light text on pink.\n */\nconst source = readFileSync(path.join(__dirname, 'create-domain-popup.ts'), 'utf-8')\n\ndescribe('the invalid-name mark in create-domain-popup', () => {\n it('★ uses the error container pair, not a fixed colour', () => {\n const rule = source.slice(source.indexOf('input.checkValidName {'), source.indexOf('}', source.indexOf('input.checkValidName {')))\n expect(rule).toMatch(/background-color:\\s*var\\(--md-sys-color-error-container\\)/)\n expect(rule).toMatch(/color:\\s*var\\(--md-sys-color-on-error-container\\)/)\n expect(rule).not.toMatch(/#[0-9a-f]{3,6}/i)\n })\n})\n"]}
|
|
@@ -24,8 +24,10 @@ let CreateDomainPopup = class CreateDomainPopup extends localize(i18next)(LitEle
|
|
|
24
24
|
padding: var(--spacing-large);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/* A name that is not valid - the theme's error pair, so the text stays readable in the dark theme. */
|
|
27
28
|
input.checkValidName {
|
|
28
|
-
background-color:
|
|
29
|
+
background-color: var(--md-sys-color-error-container);
|
|
30
|
+
color: var(--md-sys-color-on-error-container);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
label {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-domain-popup.js","sourceRoot":"","sources":["../../client/components/create-domain-popup.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGrE,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACpD,WAAM,GAAG;QACd,eAAe;QACf,kBAAkB;QAClB,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"create-domain-popup.js","sourceRoot":"","sources":["../../client/components/create-domain-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, query } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('create-domain-popup')\nclass CreateDomainPopup extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n background-color: var(--md-sys-color-background);\n overflow: auto;\n }\n\n form {\n flex: 1;\n padding: var(--spacing-large);\n }\n\n /* A name that is not valid - the theme's error pair, so the text stays readable in the dark theme. */\n input.checkValidName {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n\n label {\n display: flex;\n flex-direction: column;\n\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input {\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n background-color: var(--md-sys-color-surface);\n font: var(--input-font);\n }\n\n [field] {\n grid-column: span 2;\n }\n `\n ]\n\n @query('input[name=\"name\"]') nameInput!: HTMLInputElement\n\n render() {\n return html`\n <form>\n <div field grid-span>\n <label\n >${i18next.t('label.x name', { x: i18next.t('label.domain') })}<input\n type=\"text\"\n name=\"name\"\n @input=${this.checkValidation}\n autofocus\n /></label>\n </div>\n\n <div field grid-span>\n <label>${i18next.t('label.description')}<input type=\"text\" name=\"description\" /></label>\n </div>\n </form>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => this.onCreateDomain()} done><md-icon>add</md-icon>${i18next.t('button.create')}</button>\n </div>\n `\n }\n\n firstUpdated() {\n this.nameInput.focus() // autofocus\n }\n\n get inputData() {\n return this.renderRoot.querySelectorAll('input')\n }\n\n checkValidation(e) {\n const currentInput = e.currentTarget\n const regExp = /^[a-zA-Z ]+$/\n\n if (!regExp.test(currentInput.value)) {\n currentInput.classList.add('checkValidName')\n } else {\n currentInput.classList.remove('checkValidName')\n }\n }\n\n async onCreateDomain() {\n const domainInput: { [prop: string]: string } = {}\n this.inputData.forEach(data => (domainInput[data.name] = data.value))\n const regExp = /^[a-zA-z0-9- ]+$/\n\n if (!regExp.test(domainInput.name)) {\n return this.showToast(i18next.t('error: domain name should consist only of letters or dashes'))\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation domainRegister($domainInput: DomainGeneratorInput!) {\n domainRegister(domainInput: $domainInput) {\n id\n name\n }\n }\n `,\n variables: { domainInput }\n })\n\n if (!response.errors) {\n await OxPrompt.open({\n type: 'success',\n title: i18next.t('text.completed'),\n text: i18next.t('text.x_created_successfully', { x: i18next.t('label.domain') }),\n confirmButton: { text: i18next.t('button.confirm') }\n })\n\n history.back()\n\n this.dispatchEvent(new CustomEvent('fetch-data'))\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { type Entity360DoorAnswer, type Entity360Subject } from '@operato/layout';
|
|
3
|
+
/**
|
|
4
|
+
* The person 360 (ADR-0067, design/ui/entity360-person.md): a `User` value anywhere - a grid's
|
|
5
|
+
* `updater`, a request's author - opens it. auth-ui declares the kind because the account is its
|
|
6
|
+
* fact; organization and worklist hang their own sections on it with their own doors.
|
|
7
|
+
*
|
|
8
|
+
* This section is identity and the roles held on this site. `userRoles` answers this domain's roles
|
|
9
|
+
* only (and its parent's) - `User.roles` answers every domain's and must not be used here.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerPersonKind(): void;
|
|
12
|
+
export declare class PersonRolesSection extends LitElement {
|
|
13
|
+
static styles: import("lit").CSSResult;
|
|
14
|
+
subject?: Entity360Subject;
|
|
15
|
+
answer?: Entity360DoorAnswer<{
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}[]>;
|
|
19
|
+
updated(changed: Map<string, unknown>): void;
|
|
20
|
+
private read;
|
|
21
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import gql from 'graphql-tag';
|
|
3
|
+
import { css, html, LitElement } from 'lit';
|
|
4
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
5
|
+
import { client } from '@operato/graphql';
|
|
6
|
+
import { i18next } from '@operato/i18n';
|
|
7
|
+
import { readEntity360Door, registerEntity360Kind, registerEntity360Section } from '@operato/layout';
|
|
8
|
+
/**
|
|
9
|
+
* The person 360 (ADR-0067, design/ui/entity360-person.md): a `User` value anywhere - a grid's
|
|
10
|
+
* `updater`, a request's author - opens it. auth-ui declares the kind because the account is its
|
|
11
|
+
* fact; organization and worklist hang their own sections on it with their own doors.
|
|
12
|
+
*
|
|
13
|
+
* This section is identity and the roles held on this site. `userRoles` answers this domain's roles
|
|
14
|
+
* only (and its parent's) - `User.roles` answers every domain's and must not be used here.
|
|
15
|
+
*/
|
|
16
|
+
export function registerPersonKind() {
|
|
17
|
+
registerEntity360Kind({
|
|
18
|
+
kind: 'person',
|
|
19
|
+
typenames: ['User'],
|
|
20
|
+
title: ({ value }) => value.name || value.email || '',
|
|
21
|
+
context: ({ value }) => (value.name && value.email ? value.email : '')
|
|
22
|
+
});
|
|
23
|
+
registerEntity360Section('person', {
|
|
24
|
+
name: 'roles',
|
|
25
|
+
order: 30,
|
|
26
|
+
render: subject => html `<person-roles-section .subject=${subject}></person-roles-section>`
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
let PersonRolesSection = class PersonRolesSection extends LitElement {
|
|
30
|
+
static { this.styles = css `
|
|
31
|
+
ul {
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
list-style: none;
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: 6px;
|
|
38
|
+
}
|
|
39
|
+
li {
|
|
40
|
+
padding: 2px 8px;
|
|
41
|
+
border-radius: var(--md-sys-shape-corner-small, 8px);
|
|
42
|
+
background: var(--md-sys-color-surface-container-high);
|
|
43
|
+
color: var(--md-sys-color-on-surface);
|
|
44
|
+
font: 13px/1.5 sans-serif;
|
|
45
|
+
}
|
|
46
|
+
`; }
|
|
47
|
+
updated(changed) {
|
|
48
|
+
if (changed.has('subject'))
|
|
49
|
+
this.read();
|
|
50
|
+
}
|
|
51
|
+
async read() {
|
|
52
|
+
const id = this.subject?.id;
|
|
53
|
+
this.answer = undefined;
|
|
54
|
+
if (!id)
|
|
55
|
+
return;
|
|
56
|
+
const response = await client.query({
|
|
57
|
+
query: gql `
|
|
58
|
+
query ($userId: String!) {
|
|
59
|
+
userRoles(userId: $userId) {
|
|
60
|
+
id
|
|
61
|
+
name
|
|
62
|
+
assigned
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
variables: { userId: id }
|
|
67
|
+
});
|
|
68
|
+
/* A later subject may have arrived while this one was being read. */
|
|
69
|
+
if (this.subject?.id !== id)
|
|
70
|
+
return;
|
|
71
|
+
this.answer = readEntity360Door(response, data => (data?.userRoles || []).filter((role) => role.assigned));
|
|
72
|
+
}
|
|
73
|
+
render() {
|
|
74
|
+
const answer = this.answer;
|
|
75
|
+
return html `<ox-entity360-section
|
|
76
|
+
.heading=${i18next.t('field.roles', { defaultValue: 'roles' })}
|
|
77
|
+
.state=${answer?.state ?? 'loading'}
|
|
78
|
+
.message=${answer?.message ?? ''}
|
|
79
|
+
>
|
|
80
|
+
<ul>
|
|
81
|
+
${(answer?.data || []).map(role => html `<li>${role.name}</li>`)}
|
|
82
|
+
</ul>
|
|
83
|
+
</ox-entity360-section>`;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
__decorate([
|
|
87
|
+
property({ attribute: false }),
|
|
88
|
+
__metadata("design:type", Object)
|
|
89
|
+
], PersonRolesSection.prototype, "subject", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
state(),
|
|
92
|
+
__metadata("design:type", Object)
|
|
93
|
+
], PersonRolesSection.prototype, "answer", void 0);
|
|
94
|
+
PersonRolesSection = __decorate([
|
|
95
|
+
customElement('person-roles-section')
|
|
96
|
+
], PersonRolesSection);
|
|
97
|
+
export { PersonRolesSection };
|
|
98
|
+
//# sourceMappingURL=person-360.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person-360.js","sourceRoot":"","sources":["../../client/entity360/person-360.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport {\n readEntity360Door,\n registerEntity360Kind,\n registerEntity360Section,\n type Entity360DoorAnswer,\n type Entity360Subject\n} from '@operato/layout'\n\n/**\n * The person 360 (ADR-0067, design/ui/entity360-person.md): a `User` value anywhere - a grid's\n * `updater`, a request's author - opens it. auth-ui declares the kind because the account is its\n * fact; organization and worklist hang their own sections on it with their own doors.\n *\n * This section is identity and the roles held on this site. `userRoles` answers this domain's roles\n * only (and its parent's) - `User.roles` answers every domain's and must not be used here.\n */\n\nexport function registerPersonKind() {\n registerEntity360Kind({\n kind: 'person',\n typenames: ['User'],\n title: ({ value }) => value.name || value.email || '',\n context: ({ value }) => (value.name && value.email ? value.email : '')\n })\n\n registerEntity360Section('person', {\n name: 'roles',\n order: 30,\n render: subject => html`<person-roles-section .subject=${subject}></person-roles-section>`\n })\n}\n\n@customElement('person-roles-section')\nexport class PersonRolesSection extends LitElement {\n static styles = css`\n ul {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n li {\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 8px);\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n font: 13px/1.5 sans-serif;\n }\n `\n\n @property({ attribute: false }) subject?: Entity360Subject\n @state() answer?: Entity360DoorAnswer<{ id: string; name: string }[]>\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('subject')) this.read()\n }\n\n private async read() {\n const id = this.subject?.id\n this.answer = undefined\n if (!id) return\n\n const response = await client.query({\n query: gql`\n query ($userId: String!) {\n userRoles(userId: $userId) {\n id\n name\n assigned\n }\n }\n `,\n variables: { userId: id }\n })\n\n /* A later subject may have arrived while this one was being read. */\n if (this.subject?.id !== id) return\n this.answer = readEntity360Door(response, data => (data?.userRoles || []).filter((role: any) => role.assigned))\n }\n\n render() {\n const answer = this.answer\n return html`<ox-entity360-section\n .heading=${i18next.t('field.roles', { defaultValue: 'roles' })}\n .state=${answer?.state ?? 'loading'}\n .message=${answer?.message ?? ''}\n >\n <ul>\n ${(answer?.data || []).map(role => html`<li>${role.name}</li>`)}\n </ul>\n </ox-entity360-section>`\n }\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import '@material/web/button/text-button.js';
|
|
1
2
|
import '@material/web/icon/icon.js';
|
|
3
|
+
import '../../components/auth-badge.js';
|
|
2
4
|
import '@operato/data-grist';
|
|
3
5
|
import '@operato/oops/ox-oops-note.js';
|
|
4
6
|
import type { CSSResultGroup } from 'lit';
|
|
@@ -211,6 +213,12 @@ export declare class RoleListPage extends RoleListPageBase {
|
|
|
211
213
|
/** 템플릿에서 — 이름은 이 공장이 정한 것을 그대로 보낸다. */
|
|
212
214
|
createFromTemplate(templateId: string, name: string, separately?: boolean): any;
|
|
213
215
|
private confirmStandingSeparately;
|
|
216
|
+
private templateGapCell;
|
|
217
|
+
/**
|
|
218
|
+
* 빠진 권한을 더한다 — **무엇을 더하는지 먼저 보인다.** 권한을 여는 일이라 그 목록을 읽고 누른다.
|
|
219
|
+
* 더하는 길은 템플릿 고르개와 같다(`createRoleFromTemplate` 가 같은 이름의 역할을 채운다).
|
|
220
|
+
*/
|
|
221
|
+
private addFromTemplate;
|
|
214
222
|
/** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */
|
|
215
223
|
createBlankRole(name: string, description: string): Promise<any>;
|
|
216
224
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/text-button.js';
|
|
2
3
|
import '@material/web/icon/icon.js';
|
|
4
|
+
import '../../components/auth-badge.js';
|
|
3
5
|
import '@operato/data-grist';
|
|
4
6
|
import '@operato/oops/ox-oops-note.js';
|
|
5
7
|
import gql from 'graphql-tag';
|
|
@@ -100,25 +102,6 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
100
102
|
position: relative;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
/* 요약 배지 — 색은 짝으로, 그리고 「무엇이 나를 기다리나」에만 쓴다. */
|
|
104
|
-
.badge {
|
|
105
|
-
display: inline-block;
|
|
106
|
-
padding: 2px 8px;
|
|
107
|
-
border-radius: var(--md-sys-shape-corner-small, 6px);
|
|
108
|
-
font-size: 0.78rem;
|
|
109
|
-
white-space: nowrap;
|
|
110
|
-
background-color: var(--md-sys-color-surface-variant);
|
|
111
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
112
|
-
}
|
|
113
|
-
.badge[attention] {
|
|
114
|
-
background-color: var(--md-sys-color-error-container);
|
|
115
|
-
color: var(--md-sys-color-on-error-container);
|
|
116
|
-
}
|
|
117
|
-
/* 「우리」는 기본 상태다 — 색을 쓰면 색이 아무것도 말하지 않게 된다. */
|
|
118
|
-
.badge[quiet] {
|
|
119
|
-
background-color: transparent;
|
|
120
|
-
color: var(--md-sys-color-outline);
|
|
121
|
-
}
|
|
122
105
|
|
|
123
106
|
/*
|
|
124
107
|
── 세 묶음 ────────────────────────────────────────────────────────────
|
|
@@ -422,11 +405,11 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
422
405
|
*/
|
|
423
406
|
sourceCell(record) {
|
|
424
407
|
if (!this.isInherited(record)) {
|
|
425
|
-
return html `<
|
|
408
|
+
return html `<auth-badge tone="quiet">${i18next.t('label.ours')}</auth-badge>`;
|
|
426
409
|
}
|
|
427
410
|
const from = record?.domain?.name || record?.domain?.subdomain || '';
|
|
428
|
-
return html `<
|
|
429
|
-
>${i18next.t('label.inherited')}${from ? ` · ${from}` : ''}</
|
|
411
|
+
return html `<auth-badge title=${i18next.t('text.role belongs to x domain and is edited there', { x: from })}
|
|
412
|
+
>${i18next.t('label.inherited')}${from ? ` · ${from}` : ''}</auth-badge
|
|
430
413
|
>`;
|
|
431
414
|
}
|
|
432
415
|
async pageInitialized() {
|
|
@@ -550,6 +533,24 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
550
533
|
record: { align: 'center', renderer: privilegeCountCell },
|
|
551
534
|
width: 110
|
|
552
535
|
},
|
|
536
|
+
{
|
|
537
|
+
/*
|
|
538
|
+
* **템플릿이 그 뒤로 더 얻은 것** (ADR-0075 결정 1).
|
|
539
|
+
*
|
|
540
|
+
* 템플릿은 씨앗이라, 역할이 선 뒤에 템플릿이 얻은 권한은 그 역할에 들어가지 않는다 — plant 의
|
|
541
|
+
* 템플릿이 ai-assistant 를 얻었을 때 miratek 의 역할은 그대로였고, 그것을 본 것은 plant 의 보안
|
|
542
|
+
* 러너뿐이었다. 여기서 수를 말하고, 더하는 것은 관리자가 누른다. 자동으로 더하지 않는다.
|
|
543
|
+
*
|
|
544
|
+
* 우리 역할에만 그린다 — 상속 역할은 그 도메인의 관리자 것이다. 뒤처지지 않은 역할에는 아무것도
|
|
545
|
+
* 그리지 않는다(「최신」을 줄마다 적으면 읽을 것이 는다). 누르는 자리는 보이는 단추다 — 수를
|
|
546
|
+
* 누르게 하지 않는다(옆 칸의 주석).
|
|
547
|
+
*/
|
|
548
|
+
type: 'string',
|
|
549
|
+
name: 'templateGap',
|
|
550
|
+
header: i18next.t('field.vs template'),
|
|
551
|
+
record: { align: 'center', renderer: (_v, _c, record) => this.templateGapCell(record) },
|
|
552
|
+
width: 150
|
|
553
|
+
},
|
|
553
554
|
{
|
|
554
555
|
/*
|
|
555
556
|
* **출처 — 여기가 「보이는 것과 고칠 수 있는 것」을 가른다.**
|
|
@@ -635,6 +636,15 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
635
636
|
subdomain
|
|
636
637
|
}
|
|
637
638
|
privilegeCount
|
|
639
|
+
templateGap {
|
|
640
|
+
templateId
|
|
641
|
+
templateName
|
|
642
|
+
missingCount
|
|
643
|
+
missing {
|
|
644
|
+
category
|
|
645
|
+
axes
|
|
646
|
+
}
|
|
647
|
+
}
|
|
638
648
|
updatedAt
|
|
639
649
|
}
|
|
640
650
|
total
|
|
@@ -826,6 +836,45 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
826
836
|
cancelButton: { text: i18next.t('button.cancel') }
|
|
827
837
|
}));
|
|
828
838
|
}
|
|
839
|
+
templateGapCell(record) {
|
|
840
|
+
const gap = record?.templateGap;
|
|
841
|
+
if (!gap || !gap.missingCount || this.isInherited(record)) {
|
|
842
|
+
return nothing;
|
|
843
|
+
}
|
|
844
|
+
/*
|
|
845
|
+
* 격자 칸은 칸 요소의 shadow 안에 그려져 이 화면의 CSS 가 닿지 않는다. 그래서 스스로 모양을 갖는
|
|
846
|
+
* Material 단추를 쓰고, 높이만 칸의 단추 높이(--cell-action-height)에 맞춘다.
|
|
847
|
+
*/
|
|
848
|
+
return html `${i18next.t('text.missing against template', { count: gap.missingCount })}
|
|
849
|
+
<md-text-button
|
|
850
|
+
style="--md-text-button-container-height: var(--cell-action-height, 26px)"
|
|
851
|
+
@click=${(event) => {
|
|
852
|
+
event.stopPropagation();
|
|
853
|
+
this.addFromTemplate(record);
|
|
854
|
+
}}
|
|
855
|
+
>${i18next.t('button.add')}</md-text-button
|
|
856
|
+
>`;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* 빠진 권한을 더한다 — **무엇을 더하는지 먼저 보인다.** 권한을 여는 일이라 그 목록을 읽고 누른다.
|
|
860
|
+
* 더하는 길은 템플릿 고르개와 같다(`createRoleFromTemplate` 가 같은 이름의 역할을 채운다).
|
|
861
|
+
*/
|
|
862
|
+
async addFromTemplate(record) {
|
|
863
|
+
const gap = record.templateGap;
|
|
864
|
+
const list = gap.missing
|
|
865
|
+
.map((grant) => `${i18next.t(`privilege.category.${grant.category}`, { defaultValue: grant.category })} (${grant.axes.join(', ')})`)
|
|
866
|
+
.join(', ');
|
|
867
|
+
const confirmed = await OxPrompt.open({
|
|
868
|
+
type: 'info',
|
|
869
|
+
title: i18next.t('title.add template privileges'),
|
|
870
|
+
text: i18next.t('text.add template privileges', { template: gap.templateName, count: gap.missingCount, list }),
|
|
871
|
+
confirmButton: { text: i18next.t('button.add') },
|
|
872
|
+
cancelButton: { text: i18next.t('button.cancel') }
|
|
873
|
+
});
|
|
874
|
+
if (confirmed) {
|
|
875
|
+
await this.createFromTemplate(gap.templateId, record.name);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
829
878
|
/** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */
|
|
830
879
|
async createBlankRole(name, description) {
|
|
831
880
|
return await this.onCreateRole({ name, description });
|
|
@@ -916,7 +965,7 @@ function privilegeCountCell(_value, _column, record) {
|
|
|
916
965
|
return html ``;
|
|
917
966
|
}
|
|
918
967
|
return count === 0
|
|
919
|
-
? html `<
|
|
920
|
-
: html `<
|
|
968
|
+
? html `<auth-badge tone="attention">${i18next.t('text.no privilege granted')}</auth-badge>`
|
|
969
|
+
: html `<auth-badge>${count}</auth-badge>`;
|
|
921
970
|
}
|
|
922
971
|
//# sourceMappingURL=role-list-page.js.map
|