@things-factory/auth-ui 10.1.31 → 10.1.34
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/components/accept-invitation.d.ts +48 -0
- package/dist-client/components/accept-invitation.js +265 -0
- package/dist-client/components/accept-invitation.js.map +1 -0
- package/dist-client/components/appliance-editor.d.ts +3 -0
- package/dist-client/components/appliance-editor.js +102 -1
- package/dist-client/components/appliance-editor.js.map +1 -1
- package/dist-client/components/role-selector.js +23 -1
- package/dist-client/components/role-selector.js.map +1 -1
- package/dist-client/components/user-invite.d.ts +8 -0
- package/dist-client/components/user-invite.js +53 -6
- package/dist-client/components/user-invite.js.map +1 -1
- package/dist-client/entries/auth/accept-invitation.d.ts +3 -0
- package/dist-client/entries/auth/accept-invitation.js +10 -0
- package/dist-client/entries/auth/accept-invitation.js.map +1 -0
- package/dist-client/pages/invitation/invitation-list-query.test.d.ts +1 -0
- package/dist-client/pages/invitation/invitation-list-query.test.js +69 -0
- package/dist-client/pages/invitation/invitation-list-query.test.js.map +1 -0
- package/dist-client/pages/invitation/invitations.d.ts +63 -0
- package/dist-client/pages/invitation/invitations.js +282 -0
- package/dist-client/pages/invitation/invitations.js.map +1 -0
- package/dist-client/pages/role/role-list-page.d.ts +3 -2
- package/dist-client/pages/role/role-list-page.js +39 -6
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/credential-panel-state.d.ts +51 -0
- package/dist-client/utils/credential-panel-state.js +49 -0
- package/dist-client/utils/credential-panel-state.js.map +1 -0
- package/dist-client/utils/credential-panel-state.test.d.ts +1 -0
- package/dist-client/utils/credential-panel-state.test.js +88 -0
- package/dist-client/utils/credential-panel-state.test.js.map +1 -0
- package/dist-client/utils/inherited-name-refusal.d.ts +44 -0
- package/dist-client/utils/inherited-name-refusal.js +46 -0
- package/dist-client/utils/inherited-name-refusal.js.map +1 -0
- package/dist-client/utils/inherited-name-refusal.test.d.ts +1 -0
- package/dist-client/utils/inherited-name-refusal.test.js +60 -0
- package/dist-client/utils/inherited-name-refusal.test.js.map +1 -0
- package/dist-client/utils/invitation-token.d.ts +28 -0
- package/dist-client/utils/invitation-token.js +40 -0
- package/dist-client/utils/invitation-token.js.map +1 -0
- package/dist-client/utils/invitation-token.test.d.ts +1 -0
- package/dist-client/utils/invitation-token.test.js +60 -0
- package/dist-client/utils/invitation-token.test.js.map +1 -0
- package/dist-client/utils/role-origin.d.ts +44 -0
- package/dist-client/utils/role-origin.js +47 -0
- package/dist-client/utils/role-origin.js.map +1 -0
- package/dist-client/utils/role-origin.test.d.ts +1 -0
- package/dist-client/utils/role-origin.test.js +60 -0
- package/dist-client/utils/role-origin.test.js.map +1 -0
- package/package.json +5 -5
- package/things-factory.config.js +1 -0
- package/translations/en.json +28 -0
- package/translations/ja.json +28 -0
- package/translations/ko.json +28 -0
- package/translations/ms.json +28 -0
- package/translations/zh.json +28 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@operato/data-grist/ox-grist.js';
|
|
3
|
+
import type { FetchOption } from '@operato/data-grist';
|
|
4
|
+
import { PageView } from '@operato/shell';
|
|
5
|
+
declare const Invitations_base: (new (...args: any[]) => {
|
|
6
|
+
__preferenceProviders: {
|
|
7
|
+
[element: string]: import("@operato/p13n").PagePreferenceProvider;
|
|
8
|
+
};
|
|
9
|
+
getPagePreferenceProvider(element: string): import("@operato/p13n").PagePreferenceProvider | undefined;
|
|
10
|
+
}) & (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
11
|
+
/**
|
|
12
|
+
* Who was invited into this domain, and what became of it.
|
|
13
|
+
*
|
|
14
|
+
* ── Why this screen is the reason the table exists ──────────────────────────
|
|
15
|
+
* Accepting used to delete the row, so the moment somebody joined, the fact that they had been
|
|
16
|
+
* invited stopped existing. There was nothing to put on a screen — which is why no screen was
|
|
17
|
+
* built, and why nothing called any of the invitation doors at all. The rows stay now and carry
|
|
18
|
+
* a state, and this is what that is for.
|
|
19
|
+
*
|
|
20
|
+
* ── ⚠ The token is not a column and cannot be ───────────────────────────────
|
|
21
|
+
* The token in an invitation mail is what opens the door. This is a screen an administrator
|
|
22
|
+
* reads, so printing it here would hand every pending invitation to whoever can open the list,
|
|
23
|
+
* and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same
|
|
24
|
+
* reason `app-bindings` does not ask for a refresh token.
|
|
25
|
+
*
|
|
26
|
+
* ── The state is read as of now ─────────────────────────────────────────────
|
|
27
|
+
* `state` comes from the server's field resolver, which measures a pending row against the
|
|
28
|
+
* clock rather than trusting the stored column. A row whose expiry passed an hour ago says
|
|
29
|
+
* expired here even though nothing has been round to write that down — showing it as live would
|
|
30
|
+
* be the more dangerous of the two ways to be wrong.
|
|
31
|
+
*/
|
|
32
|
+
export declare class Invitations extends Invitations_base {
|
|
33
|
+
static styles: import("lit").CSSResult[];
|
|
34
|
+
active: boolean;
|
|
35
|
+
config: any;
|
|
36
|
+
mode: 'CARD' | 'LIST' | 'GRID';
|
|
37
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
38
|
+
failed: boolean;
|
|
39
|
+
private grist;
|
|
40
|
+
get context(): {
|
|
41
|
+
title: string;
|
|
42
|
+
help: string;
|
|
43
|
+
search: {
|
|
44
|
+
handler: (search: string) => void;
|
|
45
|
+
value: string;
|
|
46
|
+
};
|
|
47
|
+
actions: {
|
|
48
|
+
title: string;
|
|
49
|
+
action: () => void;
|
|
50
|
+
icon: string;
|
|
51
|
+
}[];
|
|
52
|
+
};
|
|
53
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
54
|
+
pageInitialized(): Promise<void>;
|
|
55
|
+
private resend;
|
|
56
|
+
private withdraw;
|
|
57
|
+
pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
|
|
58
|
+
fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
|
|
59
|
+
total: number;
|
|
60
|
+
records: any[];
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@operato/data-grist/ox-grist.js';
|
|
4
|
+
import gql from 'graphql-tag';
|
|
5
|
+
import { css, html } from 'lit';
|
|
6
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
7
|
+
import { client, gqlContext } from '@operato/graphql';
|
|
8
|
+
import { i18next, localize } from '@operato/i18n';
|
|
9
|
+
import { OxPrompt } from '@operato/popup/ox-prompt.js';
|
|
10
|
+
import { navigate, PageView } from '@operato/shell';
|
|
11
|
+
import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
|
|
12
|
+
import { isMobileDevice } from '@operato/utils';
|
|
13
|
+
import { p13n } from '@operato/p13n';
|
|
14
|
+
import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
|
|
15
|
+
/**
|
|
16
|
+
* Who was invited into this domain, and what became of it.
|
|
17
|
+
*
|
|
18
|
+
* ── Why this screen is the reason the table exists ──────────────────────────
|
|
19
|
+
* Accepting used to delete the row, so the moment somebody joined, the fact that they had been
|
|
20
|
+
* invited stopped existing. There was nothing to put on a screen — which is why no screen was
|
|
21
|
+
* built, and why nothing called any of the invitation doors at all. The rows stay now and carry
|
|
22
|
+
* a state, and this is what that is for.
|
|
23
|
+
*
|
|
24
|
+
* ── ⚠ The token is not a column and cannot be ───────────────────────────────
|
|
25
|
+
* The token in an invitation mail is what opens the door. This is a screen an administrator
|
|
26
|
+
* reads, so printing it here would hand every pending invitation to whoever can open the list,
|
|
27
|
+
* and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same
|
|
28
|
+
* reason `app-bindings` does not ask for a refresh token.
|
|
29
|
+
*
|
|
30
|
+
* ── The state is read as of now ─────────────────────────────────────────────
|
|
31
|
+
* `state` comes from the server's field resolver, which measures a pending row against the
|
|
32
|
+
* clock rather than trusting the stored column. A row whose expiry passed an hour ago says
|
|
33
|
+
* expired here even though nothing has been round to write that down — showing it as live would
|
|
34
|
+
* be the more dangerous of the two ways to be wrong.
|
|
35
|
+
*/
|
|
36
|
+
let Invitations = class Invitations extends p13n(localize(i18next)(PageView)) {
|
|
37
|
+
constructor() {
|
|
38
|
+
super(...arguments);
|
|
39
|
+
this.active = false;
|
|
40
|
+
this.mode = isMobileDevice() ? 'CARD' : 'GRID';
|
|
41
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
42
|
+
this.failed = false;
|
|
43
|
+
}
|
|
44
|
+
static { this.styles = [
|
|
45
|
+
WorkSurfacePageStyles,
|
|
46
|
+
CommonGristStyles,
|
|
47
|
+
CommonHeaderStyles,
|
|
48
|
+
ScrollbarStyles,
|
|
49
|
+
css `
|
|
50
|
+
[failed] {
|
|
51
|
+
padding: var(--spacing-medium);
|
|
52
|
+
color: var(--md-sys-color-error);
|
|
53
|
+
font: var(--label-font, inherit);
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
]; }
|
|
57
|
+
get context() {
|
|
58
|
+
return {
|
|
59
|
+
title: i18next.t('label.invitations'),
|
|
60
|
+
help: 'auth/user',
|
|
61
|
+
search: {
|
|
62
|
+
handler: (search) => {
|
|
63
|
+
this.grist.searchText = search;
|
|
64
|
+
},
|
|
65
|
+
value: this.grist?.searchText || ''
|
|
66
|
+
},
|
|
67
|
+
actions: [
|
|
68
|
+
{
|
|
69
|
+
title: i18next.t('text.users'),
|
|
70
|
+
action: () => navigate('users'),
|
|
71
|
+
icon: 'group'
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
render() {
|
|
77
|
+
return html `
|
|
78
|
+
<ox-grist
|
|
79
|
+
class="page-fill"
|
|
80
|
+
.mode=${this.mode}
|
|
81
|
+
.config=${this.config}
|
|
82
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
83
|
+
.personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
|
|
84
|
+
>
|
|
85
|
+
</ox-grist>
|
|
86
|
+
|
|
87
|
+
${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
async pageInitialized() {
|
|
91
|
+
this.config = {
|
|
92
|
+
rows: { appendable: false, selectable: { multiple: false } },
|
|
93
|
+
columns: [
|
|
94
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
95
|
+
{
|
|
96
|
+
type: 'string',
|
|
97
|
+
name: 'email',
|
|
98
|
+
header: i18next.t('field.email'),
|
|
99
|
+
record: { editable: false },
|
|
100
|
+
sortable: true,
|
|
101
|
+
width: 240
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'string',
|
|
105
|
+
name: 'state',
|
|
106
|
+
header: i18next.t('field.state'),
|
|
107
|
+
record: { align: 'center', editable: false },
|
|
108
|
+
width: 120
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'string',
|
|
112
|
+
name: 'roleNames',
|
|
113
|
+
header: i18next.t('field.roles'),
|
|
114
|
+
record: { editable: false },
|
|
115
|
+
width: 200
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'string',
|
|
119
|
+
name: 'creatorName',
|
|
120
|
+
header: i18next.t('field.invited'),
|
|
121
|
+
record: { editable: false },
|
|
122
|
+
width: 160
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'datetime',
|
|
126
|
+
name: 'createdAt',
|
|
127
|
+
header: i18next.t('field.created at'),
|
|
128
|
+
sortable: true,
|
|
129
|
+
width: 180
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'datetime',
|
|
133
|
+
name: 'expiresAt',
|
|
134
|
+
header: i18next.t('field.expires at'),
|
|
135
|
+
sortable: true,
|
|
136
|
+
width: 180
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'datetime',
|
|
140
|
+
name: 'acceptedAt',
|
|
141
|
+
header: i18next.t('field.accepted at'),
|
|
142
|
+
sortable: true,
|
|
143
|
+
width: 180
|
|
144
|
+
},
|
|
145
|
+
/*
|
|
146
|
+
* Both actions only mean anything while an invitation is still open, so they are offered
|
|
147
|
+
* only there. A button that is always present and refuses on half the rows reads as the
|
|
148
|
+
* screen being broken rather than as the row being finished.
|
|
149
|
+
*/
|
|
150
|
+
{
|
|
151
|
+
type: 'gutter',
|
|
152
|
+
gutterName: 'button',
|
|
153
|
+
icon: 'refresh',
|
|
154
|
+
title: i18next.t('button.resend'),
|
|
155
|
+
handlers: {
|
|
156
|
+
click: (_c, _d, _col, record) => record?.state === 'pending' && this.resend(record)
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'gutter',
|
|
161
|
+
gutterName: 'button',
|
|
162
|
+
icon: 'block',
|
|
163
|
+
title: i18next.t('button.withdraw'),
|
|
164
|
+
handlers: {
|
|
165
|
+
click: (_c, _d, _col, record) => record?.state === 'pending' && this.withdraw(record)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async resend(record) {
|
|
172
|
+
/*
|
|
173
|
+
* A fresh token each time. Re-mailing the old one would leave two live links for one
|
|
174
|
+
* invitation, and the older mail is the one more likely to have been forwarded.
|
|
175
|
+
*/
|
|
176
|
+
const response = await client.mutate({
|
|
177
|
+
mutation: gql `
|
|
178
|
+
mutation ($id: String!) {
|
|
179
|
+
resendInvitation(id: $id)
|
|
180
|
+
}
|
|
181
|
+
`,
|
|
182
|
+
variables: { id: record.id },
|
|
183
|
+
context: gqlContext()
|
|
184
|
+
});
|
|
185
|
+
if (!response.errors?.length) {
|
|
186
|
+
document.dispatchEvent(new CustomEvent('notify', { detail: { level: 'info', message: i18next.t('text.invitation resent') } }));
|
|
187
|
+
}
|
|
188
|
+
this.grist.fetch();
|
|
189
|
+
}
|
|
190
|
+
async withdraw(record) {
|
|
191
|
+
const confirmed = await OxPrompt.open({
|
|
192
|
+
type: 'warning',
|
|
193
|
+
title: i18next.t('button.withdraw'),
|
|
194
|
+
text: i18next.t('text.are you sure you want to delete x', { x: record.email }),
|
|
195
|
+
confirmButton: { text: i18next.t('button.withdraw') },
|
|
196
|
+
cancelButton: { text: i18next.t('button.cancel') }
|
|
197
|
+
});
|
|
198
|
+
if (!confirmed) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
await client.mutate({
|
|
202
|
+
mutation: gql `
|
|
203
|
+
mutation ($id: String!) {
|
|
204
|
+
cancelInvitation(id: $id)
|
|
205
|
+
}
|
|
206
|
+
`,
|
|
207
|
+
variables: { id: record.id },
|
|
208
|
+
context: gqlContext()
|
|
209
|
+
});
|
|
210
|
+
this.grist.fetch();
|
|
211
|
+
}
|
|
212
|
+
async pageUpdated(_changes, _lifecycle) {
|
|
213
|
+
if (this.active) {
|
|
214
|
+
await this.updateComplete;
|
|
215
|
+
this.grist.fetch();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async fetchHandler({ page, limit, sortings = [], filters = [] }) {
|
|
219
|
+
const response = await client.query({
|
|
220
|
+
query: gql `
|
|
221
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
222
|
+
invitations(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
223
|
+
items {
|
|
224
|
+
id
|
|
225
|
+
email
|
|
226
|
+
state
|
|
227
|
+
createdAt
|
|
228
|
+
expiresAt
|
|
229
|
+
acceptedAt
|
|
230
|
+
roles {
|
|
231
|
+
id
|
|
232
|
+
name
|
|
233
|
+
}
|
|
234
|
+
creator {
|
|
235
|
+
id
|
|
236
|
+
name
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
total
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
`,
|
|
243
|
+
variables: { filters, pagination: { page, limit }, sortings },
|
|
244
|
+
context: gqlContext()
|
|
245
|
+
});
|
|
246
|
+
const { items, total, failed } = listOutcome(pageFromResponse(response, 'invitations'));
|
|
247
|
+
this.failed = failed;
|
|
248
|
+
return {
|
|
249
|
+
total,
|
|
250
|
+
records: items.map((item) => ({
|
|
251
|
+
...item,
|
|
252
|
+
roleNames: (item.roles || []).map((role) => role.name).join(', '),
|
|
253
|
+
creatorName: item.creator?.name || ''
|
|
254
|
+
}))
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
__decorate([
|
|
259
|
+
property({ type: Boolean }),
|
|
260
|
+
__metadata("design:type", Boolean)
|
|
261
|
+
], Invitations.prototype, "active", void 0);
|
|
262
|
+
__decorate([
|
|
263
|
+
property({ type: Object }),
|
|
264
|
+
__metadata("design:type", Object)
|
|
265
|
+
], Invitations.prototype, "config", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
property({ type: String }),
|
|
268
|
+
__metadata("design:type", String)
|
|
269
|
+
], Invitations.prototype, "mode", void 0);
|
|
270
|
+
__decorate([
|
|
271
|
+
property({ type: Boolean }),
|
|
272
|
+
__metadata("design:type", Boolean)
|
|
273
|
+
], Invitations.prototype, "failed", void 0);
|
|
274
|
+
__decorate([
|
|
275
|
+
query('ox-grist'),
|
|
276
|
+
__metadata("design:type", Function)
|
|
277
|
+
], Invitations.prototype, "grist", void 0);
|
|
278
|
+
Invitations = __decorate([
|
|
279
|
+
customElement('invitations-page')
|
|
280
|
+
], Invitations);
|
|
281
|
+
export { Invitations };
|
|
282
|
+
//# sourceMappingURL=invitations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invitations.js","sourceRoot":"","sources":["../../../client/pages/invitation/invitations.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Who was invited into this domain, and what became of it.\n *\n * ── Why this screen is the reason the table exists ──────────────────────────\n * Accepting used to delete the row, so the moment somebody joined, the fact that they had been\n * invited stopped existing. There was nothing to put on a screen — which is why no screen was\n * built, and why nothing called any of the invitation doors at all. The rows stay now and carry\n * a state, and this is what that is for.\n *\n * ── ⚠ The token is not a column and cannot be ───────────────────────────────\n * The token in an invitation mail is what opens the door. This is a screen an administrator\n * reads, so printing it here would hand every pending invitation to whoever can open the list,\n * and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same\n * reason `app-bindings` does not ask for a refresh token.\n *\n * ── The state is read as of now ─────────────────────────────────────────────\n * `state` comes from the server's field resolver, which measures a pending row against the\n * clock rather than trusting the stored column. A row whose expiry passed an hour ago says\n * expired here even though nothing has been round to write that down — showing it as live would\n * be the more dangerous of the two ways to be wrong.\n */\n@customElement('invitations-page')\nexport class Invitations extends p13n(localize(i18next)(PageView)) {\n static styles = [\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('label.invitations'),\n help: 'auth/user',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('text.users'),\n action: () => navigate('users'),\n icon: 'group'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { editable: false },\n sortable: true,\n width: 240\n },\n {\n type: 'string',\n name: 'state',\n header: i18next.t('field.state'),\n record: { align: 'center', editable: false },\n width: 120\n },\n {\n type: 'string',\n name: 'roleNames',\n header: i18next.t('field.roles'),\n record: { editable: false },\n width: 200\n },\n {\n type: 'string',\n name: 'creatorName',\n header: i18next.t('field.invited'),\n record: { editable: false },\n width: 160\n },\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.created at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'expiresAt',\n header: i18next.t('field.expires at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'acceptedAt',\n header: i18next.t('field.accepted at'),\n sortable: true,\n width: 180\n },\n /*\n * Both actions only mean anything while an invitation is still open, so they are offered\n * only there. A button that is always present and refuses on half the rows reads as the\n * screen being broken rather than as the row being finished.\n */\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'refresh',\n title: i18next.t('button.resend'),\n handlers: {\n click: (_c: any, _d: any, _col: any, record: any) => record?.state === 'pending' && this.resend(record)\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'block',\n title: i18next.t('button.withdraw'),\n handlers: {\n click: (_c: any, _d: any, _col: any, record: any) => record?.state === 'pending' && this.withdraw(record)\n }\n }\n ]\n }\n }\n\n private async resend(record: any) {\n /*\n * A fresh token each time. Re-mailing the old one would leave two live links for one\n * invitation, and the older mail is the one more likely to have been forwarded.\n */\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n resendInvitation(id: $id)\n }\n `,\n variables: { id: record.id },\n context: gqlContext()\n })\n\n if (!response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'info', message: i18next.t('text.invitation resent') } })\n )\n }\n\n this.grist.fetch()\n }\n\n private async withdraw(record: any) {\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.withdraw'),\n text: i18next.t('text.are you sure you want to delete x', { x: record.email }),\n confirmButton: { text: i18next.t('button.withdraw') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n cancelInvitation(id: $id)\n }\n `,\n variables: { id: record.id },\n context: gqlContext()\n })\n\n this.grist.fetch()\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n invitations(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n email\n state\n createdAt\n expiresAt\n acceptedAt\n roles {\n id\n name\n }\n creator {\n id\n name\n }\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n context: gqlContext()\n })\n\n const { items, total, failed } = listOutcome(pageFromResponse(response, 'invitations'))\n\n this.failed = failed\n\n return {\n total,\n records: items.map((item: any) => ({\n ...item,\n roleNames: (item.roles || []).map((role: any) => role.name).join(', '),\n creatorName: item.creator?.name || ''\n }))\n }\n }\n}\n"]}
|
|
@@ -209,9 +209,10 @@ export declare class RoleListPage extends RoleListPageBase {
|
|
|
209
209
|
*/
|
|
210
210
|
private openCreate;
|
|
211
211
|
/** 템플릿에서 — 이름은 이 공장이 정한 것을 그대로 보낸다. */
|
|
212
|
-
createFromTemplate(templateId: string, name: string):
|
|
212
|
+
createFromTemplate(templateId: string, name: string, separately?: boolean): any;
|
|
213
|
+
private confirmStandingSeparately;
|
|
213
214
|
/** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */
|
|
214
|
-
createBlankRole(name: string, description: string): Promise<
|
|
215
|
+
createBlankRole(name: string, description: string): Promise<any>;
|
|
215
216
|
/**
|
|
216
217
|
* 새 역할.
|
|
217
218
|
*
|
|
@@ -14,6 +14,7 @@ import { InheritedValueType, navigate, PageView } from '@operato/shell';
|
|
|
14
14
|
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
|
|
15
15
|
import { isMobileDevice } from '@operato/utils';
|
|
16
16
|
import { getDomain } from '@things-factory/auth-base/dist-client';
|
|
17
|
+
import { standsInheritedRefusal } from '../../utils/inherited-name-refusal.js';
|
|
17
18
|
/**
|
|
18
19
|
* 역할 목록 — **보이는 것과 고칠 수 있는 것을 가른다.**
|
|
19
20
|
*
|
|
@@ -777,25 +778,54 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
777
778
|
});
|
|
778
779
|
}
|
|
779
780
|
/** 템플릿에서 — 이름은 이 공장이 정한 것을 그대로 보낸다. */
|
|
780
|
-
async createFromTemplate(templateId, name) {
|
|
781
|
+
async createFromTemplate(templateId, name, separately) {
|
|
781
782
|
const response = await client.mutate({
|
|
782
783
|
mutation: gql `
|
|
783
|
-
mutation ($templateId: String!, $name: String) {
|
|
784
|
-
createRoleFromTemplate(templateId: $templateId, name: $name) {
|
|
784
|
+
mutation ($templateId: String!, $name: String, $separately: Boolean) {
|
|
785
|
+
createRoleFromTemplate(templateId: $templateId, name: $name, separately: $separately) {
|
|
785
786
|
id
|
|
786
787
|
}
|
|
787
788
|
}
|
|
788
789
|
`,
|
|
789
|
-
variables: { templateId, name },
|
|
790
|
+
variables: { templateId, name, separately },
|
|
790
791
|
context: gqlContext()
|
|
791
792
|
});
|
|
792
793
|
if (response.errors?.length) {
|
|
794
|
+
if (await this.confirmStandingSeparately(response)) {
|
|
795
|
+
return await this.createFromTemplate(templateId, name, true);
|
|
796
|
+
}
|
|
793
797
|
document.dispatchEvent(new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } }));
|
|
794
798
|
return false;
|
|
795
799
|
}
|
|
796
800
|
this.grist.fetch();
|
|
797
801
|
return true;
|
|
798
802
|
}
|
|
803
|
+
/*
|
|
804
|
+
* ── The one refusal this screen can offer a way out of ──────────────────
|
|
805
|
+
* A role name already visible through the parent domain is refused so that it cannot happen
|
|
806
|
+
* silently, not because it must not happen: standing up the same names here is the first step
|
|
807
|
+
* of moving a domain off an inherited structure (ADR-0062 decisions 2 and 3). So the operator
|
|
808
|
+
* is told which domain the other one stands in and asked whether that is what they mean.
|
|
809
|
+
*
|
|
810
|
+
* The name does not change. Offering to rename instead would close the only route decision 2
|
|
811
|
+
* defines — the role standing up here is the one that replaces what is inherited, and cutting
|
|
812
|
+
* the parent last leaves one name again.
|
|
813
|
+
*
|
|
814
|
+
* Every other refusal returns false and is reported as it arrives.
|
|
815
|
+
*/
|
|
816
|
+
async confirmStandingSeparately(response) {
|
|
817
|
+
const refusal = standsInheritedRefusal(response);
|
|
818
|
+
if (!refusal) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
return !!(await OxPrompt.open({
|
|
822
|
+
type: 'warning',
|
|
823
|
+
title: i18next.t('text.a role of this name is already visible'),
|
|
824
|
+
text: i18next.t('text.stand this domain own role of the same name', refusal),
|
|
825
|
+
confirmButton: { text: i18next.t('button.stand it here') },
|
|
826
|
+
cancelButton: { text: i18next.t('button.cancel') }
|
|
827
|
+
}));
|
|
828
|
+
}
|
|
799
829
|
/** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */
|
|
800
830
|
async createBlankRole(name, description) {
|
|
801
831
|
return await this.onCreateRole({ name, description });
|
|
@@ -806,7 +836,7 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
806
836
|
* **권한 없이 만들어진다.** 그것이 맞다 — 이름을 정하는 것과 무엇을 열지 정하는 것은 다른
|
|
807
837
|
* 판단이고, 목록의 요약 칸이 「여는 것이 없음」을 빨갛게 말하므로 다음 걸음이 보인다.
|
|
808
838
|
*/
|
|
809
|
-
async onCreateRole(record) {
|
|
839
|
+
async onCreateRole(record, separately) {
|
|
810
840
|
const response = await client.mutate({
|
|
811
841
|
mutation: gql `
|
|
812
842
|
mutation ($role: NewRole!) {
|
|
@@ -815,10 +845,13 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
815
845
|
}
|
|
816
846
|
}
|
|
817
847
|
`,
|
|
818
|
-
variables: { role: { name: record.name, description: record.description } },
|
|
848
|
+
variables: { role: { name: record.name, description: record.description, separately } },
|
|
819
849
|
context: gqlContext()
|
|
820
850
|
});
|
|
821
851
|
if (response.errors?.length) {
|
|
852
|
+
if (await this.confirmStandingSeparately(response)) {
|
|
853
|
+
return await this.onCreateRole(record, true);
|
|
854
|
+
}
|
|
822
855
|
document.dispatchEvent(new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } }));
|
|
823
856
|
return false;
|
|
824
857
|
}
|