@things-factory/auth-ui 10.1.256 → 10.1.257
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 +2 -2
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/access-decision-popup.d.ts +9 -8
- package/dist-client/components/access-decision-popup.js +46 -58
- package/dist-client/components/access-decision-popup.js.map +1 -1
- package/dist-client/components/access-request-popup.d.ts +13 -8
- package/dist-client/components/access-request-popup.js +78 -102
- package/dist-client/components/access-request-popup.js.map +1 -1
- package/dist-client/components/app-binding-detail.d.ts +7 -5
- package/dist-client/components/app-binding-detail.js +62 -82
- package/dist-client/components/app-binding-detail.js.map +1 -1
- package/dist-client/components/appliance-editor.d.ts +10 -9
- package/dist-client/components/appliance-editor.js +87 -138
- package/dist-client/components/appliance-editor.js.map +1 -1
- package/dist-client/components/application-editor.d.ts +7 -5
- package/dist-client/components/application-editor.js +58 -92
- package/dist-client/components/application-editor.js.map +1 -1
- package/dist-client/components/my-login-history.js +18 -40
- package/dist-client/components/my-login-history.js.map +1 -1
- package/dist-client/components/my-passkeys.d.ts +4 -3
- package/dist-client/components/my-passkeys.js +29 -51
- package/dist-client/components/my-passkeys.js.map +1 -1
- package/dist-client/components/my-roles.d.ts +3 -4
- package/dist-client/components/my-roles.js +36 -55
- package/dist-client/components/my-roles.js.map +1 -1
- package/dist-client/components/partner-role-editor.js +27 -50
- package/dist-client/components/partner-role-editor.js.map +1 -1
- package/dist-client/components/profile-component.d.ts +2 -2
- package/dist-client/components/profile-component.js +18 -26
- package/dist-client/components/profile-component.js.map +1 -1
- package/dist-client/components/role-template-picker.d.ts +3 -3
- package/dist-client/components/role-template-picker.js +28 -42
- package/dist-client/components/role-template-picker.js.map +1 -1
- package/dist-client/components/user-detail.d.ts +23 -22
- package/dist-client/components/user-detail.js +125 -179
- package/dist-client/components/user-detail.js.map +1 -1
- package/dist-client/components/user-invite.d.ts +8 -6
- package/dist-client/components/user-invite.js +106 -141
- package/dist-client/components/user-invite.js.map +1 -1
- package/dist-client/components/user-popups-standard.test.js +1 -1
- package/dist-client/components/user-popups-standard.test.js.map +1 -1
- package/dist-client/components/user-role-editor.js +72 -122
- package/dist-client/components/user-role-editor.js.map +1 -1
- package/dist-client/entity360/person-360.d.ts +8 -11
- package/dist-client/entity360/person-360.js +49 -68
- package/dist-client/entity360/person-360.js.map +1 -1
- package/dist-client/entries/oauth2/oauth2-decision-page.js +13 -25
- package/dist-client/entries/oauth2/oauth2-decision-page.js.map +1 -1
- package/dist-client/identity-confirmation/identity-confirmation.d.ts +13 -14
- package/dist-client/identity-confirmation/identity-confirmation.js +39 -45
- package/dist-client/identity-confirmation/identity-confirmation.js.map +1 -1
- package/dist-client/pages/audit/audit-page.d.ts +4 -4
- package/dist-client/pages/audit/audit-page.js +28 -39
- package/dist-client/pages/audit/audit-page.js.map +1 -1
- package/dist-client/pages/domain-link/domain-link-management.js +1 -1
- package/dist-client/pages/domain-link/domain-link-management.js.map +1 -1
- package/dist-client/pages/partner/partner-management.js +30 -68
- package/dist-client/pages/partner/partner-management.js.map +1 -1
- package/dist-client/pages/role/role-extra-removal.test.d.mts +1 -0
- package/dist-client/pages/role/role-extra-removal.test.mjs +73 -0
- package/dist-client/pages/role/role-extra-removal.test.mjs.map +1 -0
- package/dist-client/pages/role/role-list-page.js +20 -22
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/pages/role/role-privilege-page.d.ts +36 -8
- package/dist-client/pages/role/role-privilege-page.js +216 -158
- package/dist-client/pages/role/role-privilege-page.js.map +1 -1
- package/dist-client/pages/role/role-task-review.test.js +20 -16
- package/dist-client/pages/role/role-task-review.test.js.map +1 -1
- package/dist-client/pages/role/role-template-gap.test.js +8 -3
- package/dist-client/pages/role/role-template-gap.test.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/helps/auth/roles.en.md +13 -49
- package/helps/auth/roles.ko.md +13 -32
- package/package.json +9 -8
- package/tests/page-state.test.ts +2 -1
- package/tests/password-rule-shown.test.ts +2 -2
- package/tests/person-360-sections.test.ts +9 -5
- package/translations/en.json +5 -1
- package/translations/es.json +5 -1
- package/translations/ja.json +5 -1
- package/translations/ko.json +5 -1
- package/translations/ms.json +5 -1
- package/translations/zh.json +5 -1
package/dist-client/bootstrap.js
CHANGED
|
@@ -3,7 +3,7 @@ import '@operato/i18n/ox-i18n.js';
|
|
|
3
3
|
import { i18next } from '@operato/i18n';
|
|
4
4
|
import '@operato/attribute/grist-editor'; /* for register data-grist editor type 'attributes' */
|
|
5
5
|
import { html } from 'lit-html';
|
|
6
|
-
import { navigate, removeDraftsOf, store } from '@operato/shell';
|
|
6
|
+
import { draftOwner, navigate, removeDraftsOf, store } from '@operato/shell';
|
|
7
7
|
import { openPopup, registerNotifyAction } from '@operato/layout';
|
|
8
8
|
import { OxGristEditorPrivilege } from '@operato/app/grist-editor/ox-grist-editor-privilege.js';
|
|
9
9
|
import { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer, OxGristRendererJson5 } from '@operato/data-grist';
|
|
@@ -71,7 +71,7 @@ export default async function bootstrap() {
|
|
|
71
71
|
* has none to take.
|
|
72
72
|
*/
|
|
73
73
|
auth.on('presignout', async () => {
|
|
74
|
-
const user =
|
|
74
|
+
const user = draftOwner();
|
|
75
75
|
if (user)
|
|
76
76
|
await removeDraftsOf(user).catch(() => { });
|
|
77
77
|
});
|
|
@@ -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,cAAc,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"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","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 { draftOwner, navigate, removeDraftsOf, store } from '@operato/shell'\nimport { openPopup, registerNotifyAction } 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 { hasPrivilege } from '@things-factory/auth-base/dist-client'\nimport { ADD_USER_MENU } from '@things-factory/apptool-base/client'\nimport { registerPersonAuditSection, registerPersonKind } from './entity360/person-360.js'\nimport { requestableFrom } from './utils/access-request.js'\n\nexport default async function bootstrap() {\n /* the person 360 - a User value anywhere opens it (ADR-0067) */\n registerPersonKind()\n\n /* Whose audit a person 360 shows is `audit:query`'s - only those who hold it get the section (ADR-0141 §5). */\n hasPrivilege({ privilege: 'query', category: 'audit', superUserGranted: true }).then(holds => holds && registerPersonAuditSection())\n\n registerGristEditor('privilege', OxGristEditorPrivilege)\n registerGristRenderer('privilege', OxGristRendererJson5)\n\n /*\n * The person's own profile and sign-out are in the menu the header's avatar opens, not among the\n * morendas - those are the app's administration (the rail's 「시스템 › 관리」).\n */\n store.dispatch({\n type: ADD_USER_MENU,\n item: {\n name: 'profile',\n icon: 'account_circle',\n label: 'text.auth profile',\n order: 10,\n action: () => {\n navigate('profile')\n }\n }\n })\n\n store.dispatch({\n type: ADD_USER_MENU,\n item: {\n name: 'sign-out',\n icon: 'exit_to_app',\n label: 'field.sign out',\n order: 15,\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 /*\n * Unsaved drafts stay in this browser (ADR-0098). Signing out takes this user's away, so a shared\n * computer keeps none of their edits. Awaited before the page leaves; a browser that keeps no drafts\n * has none to take.\n */\n auth.on('presignout', async () => {\n const user = draftOwner()\n if (user) await removeDraftsOf(user).catch(() => {})\n })\n\n /*\n * A refusal a role would open, of a change the person made, gets one quiet \"request access…\" on its toast. The toast\n * does not know what an error means and @operato/graphql only reports it; this package knows, so it puts the button\n * on (registerNotifyAction, things-factory ADR-0103 decision 5). Where auth-ui is not loaded there is no button.\n */\n registerNotifyAction('access-request', notification => {\n const refusal = Array.isArray(notification.ex) ? notification.ex[0] : undefined\n const detail = requestableFrom(refusal?.extensions, Array.isArray(refusal?.path) ? String(refusal.path[0]) : undefined)\n if (!detail) return null\n return {\n label: i18next.t('access-request.button.request'),\n emphasis: 'quiet',\n callback: () =>\n import('./components/access-request-popup.js').then(() =>\n openPopup(\n html`<access-request-popup\n .category=${detail.category}\n .privilege=${detail.privilege}\n .operationName=${detail.operationName}\n ></access-request-popup>`,\n { size: 'medium', title: i18next.t('access-request.title.request access') }\n )\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"]}
|
|
@@ -15,15 +15,16 @@ declare const AccessDecisionPopup_base: (new (...args: any[]) => LitElement) & t
|
|
|
15
15
|
export declare class AccessDecisionPopup extends AccessDecisionPopup_base {
|
|
16
16
|
static styles: import("lit").CSSResult[];
|
|
17
17
|
request: any;
|
|
18
|
-
|
|
19
|
-
private
|
|
18
|
+
/** The role chosen; until one is, the first candidate. */
|
|
19
|
+
private chosen?;
|
|
20
20
|
private reason;
|
|
21
|
-
private
|
|
22
|
-
private
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private
|
|
21
|
+
private granting;
|
|
22
|
+
private grantReview;
|
|
23
|
+
private denyReview;
|
|
24
|
+
private get candidates();
|
|
25
|
+
private get roleId();
|
|
26
|
+
private get grantIssues();
|
|
27
|
+
private get denyIssues();
|
|
27
28
|
render(): import("lit-html").TemplateResult<1>;
|
|
28
29
|
private decide;
|
|
29
30
|
}
|
|
@@ -7,7 +7,7 @@ import '@material/web/textfield/outlined-text-field.js';
|
|
|
7
7
|
import gql from 'graphql-tag';
|
|
8
8
|
import { css, html, LitElement, nothing } from 'lit';
|
|
9
9
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
|
-
import { client, gqlContext } from '@operato/graphql';
|
|
10
|
+
import { client, gqlContext, QueryController } from '@operato/graphql';
|
|
11
11
|
import { i18next, localize } from '@operato/i18n';
|
|
12
12
|
import { CommonPopupStyles, ScrollbarStyles } from '@operato/styles';
|
|
13
13
|
import { decisionButtons, firstCandidate, issueWords, neededWords, orderedIssues, taskSays } from '../utils/access-request.js';
|
|
@@ -31,11 +31,32 @@ const REVIEW = gql `
|
|
|
31
31
|
let AccessDecisionPopup = class AccessDecisionPopup extends localize(i18next)(LitElement) {
|
|
32
32
|
constructor() {
|
|
33
33
|
super(...arguments);
|
|
34
|
-
this.candidates = [];
|
|
35
34
|
this.reason = '';
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
this.granting = new QueryController(this, gql `
|
|
36
|
+
query ($category: String!, $privilege: String!, $taskId: String) {
|
|
37
|
+
rolesGranting(category: $category, privilege: $privilege, taskId: $taskId) {
|
|
38
|
+
id
|
|
39
|
+
name
|
|
40
|
+
privilegeCount
|
|
41
|
+
holdsTask
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`, {
|
|
45
|
+
variables: () => this.request ? { category: this.request.category, privilege: this.request.privilege, taskId: this.request.taskId || null } : undefined,
|
|
46
|
+
context: gqlContext()
|
|
47
|
+
});
|
|
48
|
+
/* The server reviews both decisions as they stand now - the role chosen, the reason typed. While a new review is out,
|
|
49
|
+
the last one stays drawn. */
|
|
50
|
+
this.grantReview = new QueryController(this, REVIEW, {
|
|
51
|
+
variables: () => this.granting.data ? { decision: { requestId: this.request.id, decision: 'GRANT', roleId: this.roleId } } : undefined,
|
|
52
|
+
context: gqlContext(),
|
|
53
|
+
keepPrevious: true
|
|
54
|
+
});
|
|
55
|
+
this.denyReview = new QueryController(this, REVIEW, {
|
|
56
|
+
variables: () => this.granting.data ? { decision: { requestId: this.request.id, decision: 'DENY', reason: this.reason } } : undefined,
|
|
57
|
+
context: gqlContext(),
|
|
58
|
+
keepPrevious: true
|
|
59
|
+
});
|
|
39
60
|
}
|
|
40
61
|
static { this.styles = [
|
|
41
62
|
CommonPopupStyles,
|
|
@@ -86,38 +107,17 @@ let AccessDecisionPopup = class AccessDecisionPopup extends localize(i18next)(Li
|
|
|
86
107
|
}
|
|
87
108
|
`
|
|
88
109
|
]; }
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
id
|
|
95
|
-
name
|
|
96
|
-
privilegeCount
|
|
97
|
-
holdsTask
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
`,
|
|
101
|
-
variables: { category: this.request.category, privilege: this.request.privilege, taskId: this.request.taskId || null },
|
|
102
|
-
context: gqlContext(),
|
|
103
|
-
fetchPolicy: 'network-only'
|
|
104
|
-
});
|
|
105
|
-
this.candidates = response.data?.rolesGranting || [];
|
|
106
|
-
this.roleId = firstCandidate(this.candidates)?.id;
|
|
107
|
-
await this.review();
|
|
110
|
+
get candidates() {
|
|
111
|
+
return this.granting.data?.rolesGranting || [];
|
|
112
|
+
}
|
|
113
|
+
get roleId() {
|
|
114
|
+
return this.chosen ?? firstCandidate(this.candidates)?.id;
|
|
108
115
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ask({ requestId: this.request.id, decision: 'GRANT', roleId: this.roleId }),
|
|
115
|
-
ask({ requestId: this.request.id, decision: 'DENY', reason: this.reason })
|
|
116
|
-
]);
|
|
117
|
-
if (asked !== this.asked)
|
|
118
|
-
return;
|
|
119
|
-
this.grantIssues = grant.errors?.length ? null : grant.data?.reviewAccessDecision || [];
|
|
120
|
-
this.denyIssues = deny.errors?.length ? null : deny.data?.reviewAccessDecision || [];
|
|
116
|
+
get grantIssues() {
|
|
117
|
+
return issuesOf(this.grantReview);
|
|
118
|
+
}
|
|
119
|
+
get denyIssues() {
|
|
120
|
+
return issuesOf(this.denyReview);
|
|
121
121
|
}
|
|
122
122
|
render() {
|
|
123
123
|
const t = i18next.t.bind(i18next);
|
|
@@ -139,10 +139,7 @@ let AccessDecisionPopup = class AccessDecisionPopup extends localize(i18next)(Li
|
|
|
139
139
|
<md-radio
|
|
140
140
|
name="role"
|
|
141
141
|
.checked=${candidate.id === this.roleId}
|
|
142
|
-
@change=${() =>
|
|
143
|
-
this.roleId = candidate.id;
|
|
144
|
-
this.review();
|
|
145
|
-
}}
|
|
142
|
+
@change=${() => (this.chosen = candidate.id)}
|
|
146
143
|
></md-radio>
|
|
147
144
|
<span>${candidate.name}</span>
|
|
148
145
|
<span count>${t('access-request.text.privilege count', { count: candidate.privilegeCount })}</span>
|
|
@@ -155,10 +152,7 @@ let AccessDecisionPopup = class AccessDecisionPopup extends localize(i18next)(Li
|
|
|
155
152
|
type="textarea"
|
|
156
153
|
rows="2"
|
|
157
154
|
.value=${this.reason}
|
|
158
|
-
@input=${(e) =>
|
|
159
|
-
this.reason = e.target.value;
|
|
160
|
-
this.review();
|
|
161
|
-
}}
|
|
155
|
+
@input=${(e) => (this.reason = e.target.value)}
|
|
162
156
|
></md-outlined-text-field>
|
|
163
157
|
${reasonIssues.length && this.reason ? reasonIssues.map(issue => html `<div issue blocking>${issueWords(t, issue)}</div>`) : nothing}
|
|
164
158
|
</div>
|
|
@@ -197,7 +191,7 @@ let AccessDecisionPopup = class AccessDecisionPopup extends localize(i18next)(Li
|
|
|
197
191
|
}
|
|
198
192
|
else {
|
|
199
193
|
/* Somebody may have decided meanwhile - show what the server says now. */
|
|
200
|
-
await this.
|
|
194
|
+
await Promise.all([this.grantReview.refetch(), this.denyReview.refetch()]);
|
|
201
195
|
}
|
|
202
196
|
}
|
|
203
197
|
};
|
|
@@ -205,28 +199,22 @@ __decorate([
|
|
|
205
199
|
property({ type: Object }),
|
|
206
200
|
__metadata("design:type", Object)
|
|
207
201
|
], AccessDecisionPopup.prototype, "request", void 0);
|
|
208
|
-
__decorate([
|
|
209
|
-
state(),
|
|
210
|
-
__metadata("design:type", Array)
|
|
211
|
-
], AccessDecisionPopup.prototype, "candidates", void 0);
|
|
212
202
|
__decorate([
|
|
213
203
|
state(),
|
|
214
204
|
__metadata("design:type", String)
|
|
215
|
-
], AccessDecisionPopup.prototype, "
|
|
205
|
+
], AccessDecisionPopup.prototype, "chosen", void 0);
|
|
216
206
|
__decorate([
|
|
217
207
|
state(),
|
|
218
208
|
__metadata("design:type", Object)
|
|
219
209
|
], AccessDecisionPopup.prototype, "reason", void 0);
|
|
220
|
-
__decorate([
|
|
221
|
-
state(),
|
|
222
|
-
__metadata("design:type", Object)
|
|
223
|
-
], AccessDecisionPopup.prototype, "grantIssues", void 0);
|
|
224
|
-
__decorate([
|
|
225
|
-
state(),
|
|
226
|
-
__metadata("design:type", Object)
|
|
227
|
-
], AccessDecisionPopup.prototype, "denyIssues", void 0);
|
|
228
210
|
AccessDecisionPopup = __decorate([
|
|
229
211
|
customElement('access-decision-popup')
|
|
230
212
|
], AccessDecisionPopup);
|
|
231
213
|
export { AccessDecisionPopup };
|
|
214
|
+
/** A review that failed says nothing - the buttons wait for one that answered. */
|
|
215
|
+
function issuesOf(review) {
|
|
216
|
+
if (review.error || !review.data)
|
|
217
|
+
return null;
|
|
218
|
+
return review.data.reviewAccessDecision || [];
|
|
219
|
+
}
|
|
232
220
|
//# sourceMappingURL=access-decision-popup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-decision-popup.js","sourceRoot":"","sources":["../../client/components/access-decision-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-popup-actions.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/radio/radio.js'\nimport '@material/web/textfield/outlined-text-field.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { CommonPopupStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { decisionButtons, firstCandidate, issueWords, neededWords, orderedIssues, taskSays, type ReviewIssue } from '../utils/access-request.js'\n\nconst REVIEW = gql`\n query ($decision: AccessDecisionInput!) {\n reviewAccessDecision(decision: $decision) {\n code\n blocking\n field\n params\n }\n }\n`\n\n/**\n * Deciding one access request (ADR-0103 · docs/design/access-request.md §6-1).\n *\n * The findings are the server's review - the same function the AI draft card and the decision door read - so\n * this popup never judges a role itself. Each change of role or reason asks again. A blocking finding takes\n * the button away; a warning stays beside it.\n */\n@customElement('access-decision-popup')\nexport class AccessDecisionPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n }\n [body] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium);\n padding: var(--spacing-large);\n }\n [who],\n [why] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [needed] {\n font: var(--title-font, inherit);\n }\n label[candidate] {\n display: flex;\n align-items: center;\n gap: var(--spacing-small);\n }\n [count] {\n color: var(--md-sys-color-on-surface-variant);\n }\n [issue][blocking] {\n color: var(--md-sys-color-error);\n }\n [issue]:not([blocking]) {\n color: var(--md-sys-color-tertiary);\n }\n [none] {\n color: var(--md-sys-color-on-surface-variant);\n }\n md-outlined-text-field {\n width: 100%;\n }\n `\n ]\n\n @property({ type: Object }) request: any\n\n @state() private candidates: { id: string; name: string; privilegeCount: number; holdsTask: boolean }[] = []\n @state() private roleId?: string\n @state() private reason = ''\n @state() private grantIssues: ReviewIssue[] | null = null\n @state() private denyIssues: ReviewIssue[] | null = null\n\n private asked = 0\n\n async firstUpdated() {\n const response = await client.query({\n query: gql`\n query ($category: String!, $privilege: String!, $taskId: String) {\n rolesGranting(category: $category, privilege: $privilege, taskId: $taskId) {\n id\n name\n privilegeCount\n holdsTask\n }\n }\n `,\n variables: { category: this.request.category, privilege: this.request.privilege, taskId: this.request.taskId || null },\n context: gqlContext(),\n fetchPolicy: 'network-only'\n })\n this.candidates = response.data?.rolesGranting || []\n this.roleId = firstCandidate(this.candidates)?.id\n await this.review()\n }\n\n /** Asks the server again; an answer to an older question is dropped. */\n private async review() {\n const asked = ++this.asked\n const ask = (decision: any) => client.query({ query: REVIEW, variables: { decision }, context: gqlContext(), fetchPolicy: 'network-only' })\n const [grant, deny] = await Promise.all([\n ask({ requestId: this.request.id, decision: 'GRANT', roleId: this.roleId }),\n ask({ requestId: this.request.id, decision: 'DENY', reason: this.reason })\n ])\n if (asked !== this.asked) return\n this.grantIssues = grant.errors?.length ? null : grant.data?.reviewAccessDecision || []\n this.denyIssues = deny.errors?.length ? null : deny.data?.reviewAccessDecision || []\n }\n\n render() {\n const t = i18next.t.bind(i18next)\n const { request } = this\n const buttons = decisionButtons(this.grantIssues, this.denyIssues)\n const reasonIssues = (this.denyIssues || []).filter(issue => issue.field === 'reason')\n\n return html`\n <div body>\n <div who>${t('access-request.text.who asks', { name: request.requester?.name || request.requester?.email || '' })}</div>\n <div needed>${neededWords(t, request)}</div>\n ${request.taskId && taskSays(t, request.taskId) ? html`<div who>${taskSays(t, request.taskId)}</div>` : nothing}\n ${request.reason ? html`<div why>${request.reason}</div>` : nothing}\n\n <div>${t('access-request.text.grant with role')}</div>\n ${this.candidates.length === 0\n ? html`<div none>${t('access-request.text.no role grants it')}</div>`\n : this.candidates.map(\n candidate => html`\n <label candidate>\n <md-radio\n name=\"role\"\n .checked=${candidate.id === this.roleId}\n @change=${() => {\n this.roleId = candidate.id\n this.review()\n }}\n ></md-radio>\n <span>${candidate.name}</span>\n <span count>${t('access-request.text.privilege count', { count: candidate.privilegeCount })}</span>\n </label>\n `\n )}\n ${orderedIssues(this.grantIssues || []).map(issue => html`<div issue ?blocking=${issue.blocking}>${issueWords(t, issue)}</div>`)}\n\n <md-outlined-text-field\n label=${t('access-request.field.deny reason')}\n type=\"textarea\"\n rows=\"2\"\n .value=${this.reason}\n @input=${(e: any) => {\n this.reason = e.target.value\n this.review()\n }}\n ></md-outlined-text-field>\n ${reasonIssues.length && this.reason ? reasonIssues.map(issue => html`<div issue blocking>${issueWords(t, issue)}</div>`) : nothing}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => history.back()}>${t('button.cancel')}</md-text-button>\n <md-text-button slot=\"start\" ?disabled=${!buttons.deny} @click=${() => this.decide('DENY')}>\n ${t('access-request.button.deny')}\n </md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!buttons.grant} @click=${() => this.decide('GRANT')}>\n ${t('access-request.button.grant')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private async decide(decision: 'GRANT' | 'DENY') {\n const response = await client.mutate({\n mutation: gql`\n mutation ($decision: AccessDecisionInput!) {\n decideAccessRequest(decision: $decision) {\n id\n state\n }\n }\n `,\n variables: {\n decision:\n decision === 'GRANT'\n ? { requestId: this.request.id, decision, roleId: this.roleId }\n : { requestId: this.request.id, decision, reason: this.reason }\n },\n context: gqlContext()\n })\n\n if (!response.errors?.length) {\n this.dispatchEvent(new CustomEvent('access-request-decided', { bubbles: true, composed: true }))\n history.back()\n } else {\n /* Somebody may have decided meanwhile - show what the server says now. */\n await this.review()\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"access-decision-popup.js","sourceRoot":"","sources":["../../client/components/access-decision-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-popup-actions.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/radio/radio.js'\nimport '@material/web/textfield/outlined-text-field.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client, gqlContext, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { CommonPopupStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { decisionButtons, firstCandidate, issueWords, neededWords, orderedIssues, taskSays, type ReviewIssue } from '../utils/access-request.js'\n\nconst REVIEW = gql`\n query ($decision: AccessDecisionInput!) {\n reviewAccessDecision(decision: $decision) {\n code\n blocking\n field\n params\n }\n }\n`\n\n/**\n * Deciding one access request (ADR-0103 · docs/design/access-request.md §6-1).\n *\n * The findings are the server's review - the same function the AI draft card and the decision door read - so\n * this popup never judges a role itself. Each change of role or reason asks again. A blocking finding takes\n * the button away; a warning stays beside it.\n */\n@customElement('access-decision-popup')\nexport class AccessDecisionPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n }\n [body] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium);\n padding: var(--spacing-large);\n }\n [who],\n [why] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [needed] {\n font: var(--title-font, inherit);\n }\n label[candidate] {\n display: flex;\n align-items: center;\n gap: var(--spacing-small);\n }\n [count] {\n color: var(--md-sys-color-on-surface-variant);\n }\n [issue][blocking] {\n color: var(--md-sys-color-error);\n }\n [issue]:not([blocking]) {\n color: var(--md-sys-color-tertiary);\n }\n [none] {\n color: var(--md-sys-color-on-surface-variant);\n }\n md-outlined-text-field {\n width: 100%;\n }\n `\n ]\n\n @property({ type: Object }) request: any\n\n /** The role chosen; until one is, the first candidate. */\n @state() private chosen?: string\n @state() private reason = ''\n\n private granting = new QueryController(\n this,\n gql`\n query ($category: String!, $privilege: String!, $taskId: String) {\n rolesGranting(category: $category, privilege: $privilege, taskId: $taskId) {\n id\n name\n privilegeCount\n holdsTask\n }\n }\n `,\n {\n variables: () =>\n this.request ? { category: this.request.category, privilege: this.request.privilege, taskId: this.request.taskId || null } : undefined,\n context: gqlContext()\n }\n )\n\n /* The server reviews both decisions as they stand now - the role chosen, the reason typed. While a new review is out,\n the last one stays drawn. */\n private grantReview = new QueryController(this, REVIEW, {\n variables: () =>\n this.granting.data ? { decision: { requestId: this.request.id, decision: 'GRANT', roleId: this.roleId } } : undefined,\n context: gqlContext(),\n keepPrevious: true\n })\n\n private denyReview = new QueryController(this, REVIEW, {\n variables: () =>\n this.granting.data ? { decision: { requestId: this.request.id, decision: 'DENY', reason: this.reason } } : undefined,\n context: gqlContext(),\n keepPrevious: true\n })\n\n private get candidates(): { id: string; name: string; privilegeCount: number; holdsTask: boolean }[] {\n return this.granting.data?.rolesGranting || []\n }\n\n private get roleId(): string | undefined {\n return this.chosen ?? firstCandidate(this.candidates)?.id\n }\n\n private get grantIssues(): ReviewIssue[] | null {\n return issuesOf(this.grantReview)\n }\n\n private get denyIssues(): ReviewIssue[] | null {\n return issuesOf(this.denyReview)\n }\n\n render() {\n const t = i18next.t.bind(i18next)\n const { request } = this\n const buttons = decisionButtons(this.grantIssues, this.denyIssues)\n const reasonIssues = (this.denyIssues || []).filter(issue => issue.field === 'reason')\n\n return html`\n <div body>\n <div who>${t('access-request.text.who asks', { name: request.requester?.name || request.requester?.email || '' })}</div>\n <div needed>${neededWords(t, request)}</div>\n ${request.taskId && taskSays(t, request.taskId) ? html`<div who>${taskSays(t, request.taskId)}</div>` : nothing}\n ${request.reason ? html`<div why>${request.reason}</div>` : nothing}\n\n <div>${t('access-request.text.grant with role')}</div>\n ${this.candidates.length === 0\n ? html`<div none>${t('access-request.text.no role grants it')}</div>`\n : this.candidates.map(\n candidate => html`\n <label candidate>\n <md-radio\n name=\"role\"\n .checked=${candidate.id === this.roleId}\n @change=${() => (this.chosen = candidate.id)}\n ></md-radio>\n <span>${candidate.name}</span>\n <span count>${t('access-request.text.privilege count', { count: candidate.privilegeCount })}</span>\n </label>\n `\n )}\n ${orderedIssues(this.grantIssues || []).map(issue => html`<div issue ?blocking=${issue.blocking}>${issueWords(t, issue)}</div>`)}\n\n <md-outlined-text-field\n label=${t('access-request.field.deny reason')}\n type=\"textarea\"\n rows=\"2\"\n .value=${this.reason}\n @input=${(e: any) => (this.reason = e.target.value)}\n ></md-outlined-text-field>\n ${reasonIssues.length && this.reason ? reasonIssues.map(issue => html`<div issue blocking>${issueWords(t, issue)}</div>`) : nothing}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => history.back()}>${t('button.cancel')}</md-text-button>\n <md-text-button slot=\"start\" ?disabled=${!buttons.deny} @click=${() => this.decide('DENY')}>\n ${t('access-request.button.deny')}\n </md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!buttons.grant} @click=${() => this.decide('GRANT')}>\n ${t('access-request.button.grant')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private async decide(decision: 'GRANT' | 'DENY') {\n const response = await client.mutate({\n mutation: gql`\n mutation ($decision: AccessDecisionInput!) {\n decideAccessRequest(decision: $decision) {\n id\n state\n }\n }\n `,\n variables: {\n decision:\n decision === 'GRANT'\n ? { requestId: this.request.id, decision, roleId: this.roleId }\n : { requestId: this.request.id, decision, reason: this.reason }\n },\n context: gqlContext()\n })\n\n if (!response.errors?.length) {\n this.dispatchEvent(new CustomEvent('access-request-decided', { bubbles: true, composed: true }))\n history.back()\n } else {\n /* Somebody may have decided meanwhile - show what the server says now. */\n await Promise.all([this.grantReview.refetch(), this.denyReview.refetch()])\n }\n }\n}\n\n/** A review that failed says nothing - the buttons wait for one that answered. */\nfunction issuesOf(review: QueryController): ReviewIssue[] | null {\n if (review.error || !review.data) return null\n return review.data.reviewAccessDecision || []\n}\n"]}
|
|
@@ -20,18 +20,23 @@ export declare class AccessRequestPopup extends AccessRequestPopup_base {
|
|
|
20
20
|
category: string;
|
|
21
21
|
privilege: string;
|
|
22
22
|
operationName?: string;
|
|
23
|
+
/** What the person typed; until then, the reason of the request already waiting. */
|
|
24
|
+
private typed?;
|
|
25
|
+
/** The task chosen; until one is, the one the waiting request named, else the first. */
|
|
26
|
+
private chosen?;
|
|
27
|
+
private standing;
|
|
28
|
+
private request;
|
|
29
|
+
private get mine();
|
|
23
30
|
/** The request of this person and privilege that is still waiting, if any. */
|
|
24
|
-
private waiting;
|
|
31
|
+
private get waiting();
|
|
25
32
|
/** A denial of the same privilege in the last days - its reason is shown before the person writes (Q9 · 5). */
|
|
26
|
-
private denied;
|
|
33
|
+
private get denied();
|
|
27
34
|
/** How many people a request reaches - said before sending (Q9 · 2). */
|
|
28
|
-
private deciders;
|
|
29
|
-
private reason;
|
|
30
|
-
private sending;
|
|
35
|
+
private get deciders();
|
|
36
|
+
private get reason();
|
|
31
37
|
/** The tasks that hold the refused privilege, fewest privileges first - the server's order (tasksGranting). */
|
|
32
|
-
private tasks;
|
|
33
|
-
private taskId
|
|
34
|
-
firstUpdated(): Promise<void>;
|
|
38
|
+
private get tasks();
|
|
39
|
+
private get taskId();
|
|
35
40
|
render(): import("lit-html").TemplateResult<1>;
|
|
36
41
|
/** The work the refused privilege belongs to - one line for one task, a choice for several, the privilege for none. */
|
|
37
42
|
private renderNeeded;
|
|
@@ -6,7 +6,7 @@ import '@material/web/textfield/outlined-text-field.js';
|
|
|
6
6
|
import gql from 'graphql-tag';
|
|
7
7
|
import { css, html, LitElement } from 'lit';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
|
-
import {
|
|
9
|
+
import { gqlContext, MutationController, QueryController } from '@operato/graphql';
|
|
10
10
|
import { i18next, localize } from '@operato/i18n';
|
|
11
11
|
import { CommonPopupStyles } from '@operato/styles';
|
|
12
12
|
import '@material/web/radio/radio.js';
|
|
@@ -26,16 +26,37 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
26
26
|
super(...arguments);
|
|
27
27
|
this.category = '';
|
|
28
28
|
this.privilege = '';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
this.standing = new QueryController(this, gql `
|
|
30
|
+
query ($category: String!, $privilege: String!) {
|
|
31
|
+
myAccessRequests {
|
|
32
|
+
id
|
|
33
|
+
category
|
|
34
|
+
privilege
|
|
35
|
+
taskId
|
|
36
|
+
state
|
|
37
|
+
reason
|
|
38
|
+
count
|
|
39
|
+
lastRequestedAt
|
|
40
|
+
decidedAt
|
|
41
|
+
decisionReason
|
|
42
|
+
}
|
|
43
|
+
accessRequestDeciderCount
|
|
44
|
+
tasksGranting(category: $category, privilege: $privilege) {
|
|
45
|
+
taskId
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`, {
|
|
49
|
+
variables: () => (this.category && this.privilege ? { category: this.category, privilege: this.privilege } : undefined),
|
|
50
|
+
context: gqlContext()
|
|
51
|
+
});
|
|
52
|
+
this.request = new MutationController(this, gql `
|
|
53
|
+
mutation ($request: NewAccessRequest!) {
|
|
54
|
+
requestAccess(request: $request) {
|
|
55
|
+
id
|
|
56
|
+
count
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`, { context: gqlContext() });
|
|
39
60
|
}
|
|
40
61
|
static { this.styles = [
|
|
41
62
|
CommonPopupStyles,
|
|
@@ -89,40 +110,32 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
89
110
|
}
|
|
90
111
|
`
|
|
91
112
|
]; }
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const mine = response.data?.myAccessRequests || [];
|
|
119
|
-
this.waiting = mine.find((r) => r.state === 'PENDING' && r.category === this.category && r.privilege === this.privilege) || null;
|
|
120
|
-
this.denied = this.waiting ? null : recentDenial(mine, this.category, this.privilege) || null;
|
|
121
|
-
this.deciders = typeof response.data?.accessRequestDeciderCount === 'number' ? response.data.accessRequestDeciderCount : null;
|
|
122
|
-
this.reason = this.waiting?.reason || '';
|
|
123
|
-
this.tasks = response.data?.tasksGranting || [];
|
|
124
|
-
/* A request already waiting keeps the work it named - asking again adds to it and does not change it. */
|
|
125
|
-
this.taskId = this.tasks.find(task => task.taskId === this.waiting?.taskId)?.taskId ?? this.tasks[0]?.taskId;
|
|
113
|
+
get mine() {
|
|
114
|
+
return this.standing.data?.myAccessRequests || [];
|
|
115
|
+
}
|
|
116
|
+
/** The request of this person and privilege that is still waiting, if any. */
|
|
117
|
+
get waiting() {
|
|
118
|
+
return this.mine.find((r) => r.state === 'PENDING' && r.category === this.category && r.privilege === this.privilege) || null;
|
|
119
|
+
}
|
|
120
|
+
/** A denial of the same privilege in the last days - its reason is shown before the person writes (Q9 · 5). */
|
|
121
|
+
get denied() {
|
|
122
|
+
return this.waiting ? null : recentDenial(this.mine, this.category, this.privilege) || null;
|
|
123
|
+
}
|
|
124
|
+
/** How many people a request reaches - said before sending (Q9 · 2). */
|
|
125
|
+
get deciders() {
|
|
126
|
+
const count = this.standing.data?.accessRequestDeciderCount;
|
|
127
|
+
return typeof count === 'number' ? count : null;
|
|
128
|
+
}
|
|
129
|
+
get reason() {
|
|
130
|
+
return this.typed ?? (this.waiting?.reason || '');
|
|
131
|
+
}
|
|
132
|
+
/** The tasks that hold the refused privilege, fewest privileges first - the server's order (tasksGranting). */
|
|
133
|
+
get tasks() {
|
|
134
|
+
return this.standing.data?.tasksGranting || [];
|
|
135
|
+
}
|
|
136
|
+
/* A request already waiting keeps the work it named - asking again adds to it and does not change it. */
|
|
137
|
+
get taskId() {
|
|
138
|
+
return this.chosen ?? this.tasks.find(task => task.taskId === this.waiting?.taskId)?.taskId ?? this.tasks[0]?.taskId;
|
|
126
139
|
}
|
|
127
140
|
render() {
|
|
128
141
|
return html `
|
|
@@ -150,7 +163,7 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
150
163
|
type="textarea"
|
|
151
164
|
rows="2"
|
|
152
165
|
.value=${this.reason}
|
|
153
|
-
@input=${(e) => (this.
|
|
166
|
+
@input=${(e) => (this.typed = e.target.value)}
|
|
154
167
|
></md-outlined-text-field>
|
|
155
168
|
${this.deciders === null
|
|
156
169
|
? ''
|
|
@@ -163,7 +176,7 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
163
176
|
|
|
164
177
|
<ox-popup-actions>
|
|
165
178
|
<md-text-button slot="cancel" @click=${() => history.back()}>${i18next.t('button.cancel')}</md-text-button>
|
|
166
|
-
<md-filled-button slot="confirm" ?disabled=${this.
|
|
179
|
+
<md-filled-button slot="confirm" ?disabled=${this.request.pending || !maySend(this.reason, this.denied) || this.deciders === 0} @click=${this.send}>
|
|
167
180
|
${i18next.t('access-request.button.send')}
|
|
168
181
|
</md-filled-button>
|
|
169
182
|
</ox-popup-actions>
|
|
@@ -181,7 +194,7 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
181
194
|
}
|
|
182
195
|
return this.tasks.map(task => html `
|
|
183
196
|
<label task>
|
|
184
|
-
<md-radio name="task" .checked=${task.taskId === this.taskId} @change=${() => (this.
|
|
197
|
+
<md-radio name="task" .checked=${task.taskId === this.taskId} @change=${() => (this.chosen = task.taskId)}></md-radio>
|
|
185
198
|
<span>
|
|
186
199
|
<div choice>${taskName(t, task.taskId)}</div>
|
|
187
200
|
${taskSays(t, task.taskId) ? html `<div says>${taskSays(t, task.taskId)}</div>` : ''}
|
|
@@ -190,38 +203,21 @@ let AccessRequestPopup = class AccessRequestPopup extends localize(i18next)(LitE
|
|
|
190
203
|
`);
|
|
191
204
|
}
|
|
192
205
|
async send() {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
count
|
|
206
|
+
const sent = await this.request.run({
|
|
207
|
+
request: {
|
|
208
|
+
category: this.category,
|
|
209
|
+
privilege: this.privilege,
|
|
210
|
+
operationName: this.operationName,
|
|
211
|
+
taskId: this.taskId,
|
|
212
|
+
reason: this.reason.trim()
|
|
201
213
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
taskId: this.taskId,
|
|
210
|
-
reason: this.reason.trim()
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
context: gqlContext()
|
|
214
|
-
});
|
|
215
|
-
if (!response.errors?.length) {
|
|
216
|
-
const merged = (response.data?.requestAccess?.count || 1) > 1;
|
|
217
|
-
document.dispatchEvent(new CustomEvent('notify', {
|
|
218
|
-
detail: { level: 'info', message: i18next.t(merged ? 'access-request.text.added to waiting' : 'access-request.text.sent') }
|
|
219
|
-
}));
|
|
220
|
-
history.back();
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
finally {
|
|
224
|
-
this.sending = false;
|
|
214
|
+
});
|
|
215
|
+
if (sent) {
|
|
216
|
+
const merged = (sent.requestAccess?.count || 1) > 1;
|
|
217
|
+
document.dispatchEvent(new CustomEvent('notify', {
|
|
218
|
+
detail: { level: 'info', message: i18next.t(merged ? 'access-request.text.added to waiting' : 'access-request.text.sent') }
|
|
219
|
+
}));
|
|
220
|
+
history.back();
|
|
225
221
|
}
|
|
226
222
|
}
|
|
227
223
|
};
|
|
@@ -239,32 +235,12 @@ __decorate([
|
|
|
239
235
|
], AccessRequestPopup.prototype, "operationName", void 0);
|
|
240
236
|
__decorate([
|
|
241
237
|
state(),
|
|
242
|
-
__metadata("design:type",
|
|
243
|
-
], AccessRequestPopup.prototype, "
|
|
244
|
-
__decorate([
|
|
245
|
-
state(),
|
|
246
|
-
__metadata("design:type", Object)
|
|
247
|
-
], AccessRequestPopup.prototype, "denied", void 0);
|
|
248
|
-
__decorate([
|
|
249
|
-
state(),
|
|
250
|
-
__metadata("design:type", Object)
|
|
251
|
-
], AccessRequestPopup.prototype, "deciders", void 0);
|
|
252
|
-
__decorate([
|
|
253
|
-
state(),
|
|
254
|
-
__metadata("design:type", Object)
|
|
255
|
-
], AccessRequestPopup.prototype, "reason", void 0);
|
|
256
|
-
__decorate([
|
|
257
|
-
state(),
|
|
258
|
-
__metadata("design:type", Object)
|
|
259
|
-
], AccessRequestPopup.prototype, "sending", void 0);
|
|
260
|
-
__decorate([
|
|
261
|
-
state(),
|
|
262
|
-
__metadata("design:type", Array)
|
|
263
|
-
], AccessRequestPopup.prototype, "tasks", void 0);
|
|
238
|
+
__metadata("design:type", String)
|
|
239
|
+
], AccessRequestPopup.prototype, "typed", void 0);
|
|
264
240
|
__decorate([
|
|
265
241
|
state(),
|
|
266
242
|
__metadata("design:type", String)
|
|
267
|
-
], AccessRequestPopup.prototype, "
|
|
243
|
+
], AccessRequestPopup.prototype, "chosen", void 0);
|
|
268
244
|
AccessRequestPopup = __decorate([
|
|
269
245
|
customElement('access-request-popup')
|
|
270
246
|
], AccessRequestPopup);
|