@things-factory/auth-ui 10.1.256 → 10.1.258
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 +11 -10
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-request-popup.js","sourceRoot":"","sources":["../../client/components/access-request-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/textfield/outlined-text-field.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } 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 } from '@operato/styles'\n\nimport '@material/web/radio/radio.js'\n\nimport { maySend, privilegeWords, recentDenial, taskName, taskSays } from '../utils/access-request.js'\n\n/**\n * Asking for a privilege the person was just refused (ADR-0103, docs/design/access-request.md §3-2).\n *\n * What was needed comes from the refusal - the person writes only why, and must (questions Q9). It is said as the work\n * it belongs to: every task holding the refused privilege is listed, fewest privileges first, the first picked, and the\n * person picks the one they were doing (ADR-0103 decision 9). A privilege no task holds is said as itself. Before sending\n * the popup says the request is recorded and how many people it reaches; a denial of the last days is shown with\n * its reason, and the same words are not sent again. A request already waiting is shown instead of a blank form:\n * pressing again adds to it, the people deciding are not told twice.\n */\n@customElement('access-request-popup')\nexport class AccessRequestPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n }\n /* min-height: 0 lets the body scroll inside the popup, so the send button below it stays in view. */\n [body] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium);\n padding: var(--spacing-large);\n }\n [needed] {\n font: var(--title-font, inherit);\n color: var(--md-sys-color-on-surface);\n }\n /* A choice is a line like the decision popup's role candidates - body size; the one task alone is the title. */\n label[task] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-small);\n }\n label[task] [choice] {\n font-weight: 600;\n color: var(--md-sys-color-on-surface);\n }\n [says] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [operation],\n [waiting],\n [recorded] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [denied] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-error);\n }\n md-outlined-text-field {\n width: 100%;\n }\n `\n ]\n\n @property({ type: String }) category: string = ''\n @property({ type: String }) privilege: string = ''\n @property({ type: String }) operationName?: string\n\n /** The request of this person and privilege that is still waiting, if any. */\n @state() private waiting: any = null\n /** A denial of the same privilege in the last days - its reason is shown before the person writes (Q9 · 5). */\n @state() private denied: any = null\n /** How many people a request reaches - said before sending (Q9 · 2). */\n @state() private deciders: number | null = null\n @state() private reason = ''\n @state() private sending = false\n /** The tasks that hold the refused privilege, fewest privileges first - the server's order (tasksGranting). */\n @state() private tasks: { taskId: string }[] = []\n @state() private taskId?: string\n\n async firstUpdated() {\n const response = await client.query({\n query: gql`\n query ($category: String!, $privilege: String!) {\n myAccessRequests {\n id\n category\n privilege\n taskId\n state\n reason\n count\n lastRequestedAt\n decidedAt\n decisionReason\n }\n accessRequestDeciderCount\n tasksGranting(category: $category, privilege: $privilege) {\n taskId\n }\n }\n `,\n variables: { category: this.category, privilege: this.privilege },\n context: gqlContext(),\n fetchPolicy: 'network-only'\n })\n const mine = response.data?.myAccessRequests || []\n this.waiting = mine.find((r: any) => r.state === 'PENDING' && r.category === this.category && r.privilege === this.privilege) || null\n this.denied = this.waiting ? null : recentDenial(mine, this.category, this.privilege) || null\n this.deciders = typeof response.data?.accessRequestDeciderCount === 'number' ? response.data.accessRequestDeciderCount : null\n this.reason = this.waiting?.reason || ''\n this.tasks = response.data?.tasksGranting || []\n /* A request already waiting keeps the work it named - asking again adds to it and does not change it. */\n this.taskId = this.tasks.find(task => task.taskId === this.waiting?.taskId)?.taskId ?? this.tasks[0]?.taskId\n }\n\n render() {\n return html`\n <div body>\n ${this.renderNeeded()}\n ${this.operationName\n ? html`<div operation>${i18next.t('access-request.text.refused at', { operation: this.operationName })}</div>`\n : ''}\n ${this.waiting\n ? html`<div waiting>\n ${i18next.t('access-request.text.already waiting', { at: new Date(this.waiting.lastRequestedAt).toLocaleString() })}\n </div>`\n : ''}\n ${this.denied\n ? html`<div denied>\n ${i18next.t('access-request.text.denied lately', {\n at: new Date(this.denied.decidedAt).toLocaleDateString(),\n reason: this.denied.decisionReason || ''\n })}\n </div>`\n : ''}\n <md-outlined-text-field\n label=${i18next.t('access-request.field.reason')}\n required\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 ${this.deciders === null\n ? ''\n : html`<div recorded>\n ${this.deciders > 0\n ? i18next.t('access-request.text.recorded and told', { count: this.deciders })\n : i18next.t('access-request.text.nobody to tell')}\n </div>`}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => history.back()}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.sending || !maySend(this.reason, this.denied) || this.deciders === 0} @click=${this.send}>\n ${i18next.t('access-request.button.send')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /** The work the refused privilege belongs to - one line for one task, a choice for several, the privilege for none. */\n private renderNeeded() {\n const t = i18next.t.bind(i18next)\n if (this.tasks.length === 0) return html`<div needed>${privilegeWords(t, this.category, this.privilege)}</div>`\n if (this.tasks.length === 1) {\n const says = taskSays(t, this.tasks[0].taskId)\n return html`<div needed>${taskName(t, this.tasks[0].taskId)}</div>\n ${says ? html`<div says>${says}</div>` : ''}`\n }\n return this.tasks.map(\n task => html`\n <label task>\n <md-radio name=\"task\" .checked=${task.taskId === this.taskId} @change=${() => (this.taskId = task.taskId)}></md-radio>\n <span>\n <div choice>${taskName(t, task.taskId)}</div>\n ${taskSays(t, task.taskId) ? html`<div says>${taskSays(t, task.taskId)}</div>` : ''}\n </span>\n </label>\n `\n )\n }\n\n private async send() {\n this.sending = true\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($request: NewAccessRequest!) {\n requestAccess(request: $request) {\n id\n count\n }\n }\n `,\n variables: {\n request: {\n category: this.category,\n privilege: this.privilege,\n operationName: this.operationName,\n taskId: this.taskId,\n reason: this.reason.trim()\n }\n },\n context: gqlContext()\n })\n\n if (!response.errors?.length) {\n const merged = (response.data?.requestAccess?.count || 1) > 1\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: { level: 'info', message: i18next.t(merged ? 'access-request.text.added to waiting' : 'access-request.text.sent') }\n })\n )\n history.back()\n }\n } finally {\n this.sending = false\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"access-request-popup.js","sourceRoot":"","sources":["../../client/components/access-request-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/textfield/outlined-text-field.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { gqlContext, MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { CommonPopupStyles } from '@operato/styles'\n\nimport '@material/web/radio/radio.js'\n\nimport { maySend, privilegeWords, recentDenial, taskName, taskSays } from '../utils/access-request.js'\n\n/**\n * Asking for a privilege the person was just refused (ADR-0103, docs/design/access-request.md §3-2).\n *\n * What was needed comes from the refusal - the person writes only why, and must (questions Q9). It is said as the work\n * it belongs to: every task holding the refused privilege is listed, fewest privileges first, the first picked, and the\n * person picks the one they were doing (ADR-0103 decision 9). A privilege no task holds is said as itself. Before sending\n * the popup says the request is recorded and how many people it reaches; a denial of the last days is shown with\n * its reason, and the same words are not sent again. A request already waiting is shown instead of a blank form:\n * pressing again adds to it, the people deciding are not told twice.\n */\n@customElement('access-request-popup')\nexport class AccessRequestPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n }\n /* min-height: 0 lets the body scroll inside the popup, so the send button below it stays in view. */\n [body] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium);\n padding: var(--spacing-large);\n }\n [needed] {\n font: var(--title-font, inherit);\n color: var(--md-sys-color-on-surface);\n }\n /* A choice is a line like the decision popup's role candidates - body size; the one task alone is the title. */\n label[task] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-small);\n }\n label[task] [choice] {\n font-weight: 600;\n color: var(--md-sys-color-on-surface);\n }\n [says] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [operation],\n [waiting],\n [recorded] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant);\n }\n [denied] {\n font: var(--label-font, inherit);\n color: var(--md-sys-color-error);\n }\n md-outlined-text-field {\n width: 100%;\n }\n `\n ]\n\n @property({ type: String }) category: string = ''\n @property({ type: String }) privilege: string = ''\n @property({ type: String }) operationName?: string\n\n /** What the person typed; until then, the reason of the request already waiting. */\n @state() private typed?: string\n /** The task chosen; until one is, the one the waiting request named, else the first. */\n @state() private chosen?: string\n\n private standing = new QueryController(\n this,\n gql`\n query ($category: String!, $privilege: String!) {\n myAccessRequests {\n id\n category\n privilege\n taskId\n state\n reason\n count\n lastRequestedAt\n decidedAt\n decisionReason\n }\n accessRequestDeciderCount\n tasksGranting(category: $category, privilege: $privilege) {\n taskId\n }\n }\n `,\n {\n variables: () => (this.category && this.privilege ? { category: this.category, privilege: this.privilege } : undefined),\n context: gqlContext()\n }\n )\n\n private request = new MutationController(\n this,\n gql`\n mutation ($request: NewAccessRequest!) {\n requestAccess(request: $request) {\n id\n count\n }\n }\n `,\n { context: gqlContext() }\n )\n\n private get mine(): any[] {\n return this.standing.data?.myAccessRequests || []\n }\n\n /** The request of this person and privilege that is still waiting, if any. */\n private get waiting(): any {\n return this.mine.find((r: any) => r.state === 'PENDING' && r.category === this.category && r.privilege === this.privilege) || null\n }\n\n /** A denial of the same privilege in the last days - its reason is shown before the person writes (Q9 · 5). */\n private get denied(): any {\n return this.waiting ? null : recentDenial(this.mine, this.category, this.privilege) || null\n }\n\n /** How many people a request reaches - said before sending (Q9 · 2). */\n private get deciders(): number | null {\n const count = this.standing.data?.accessRequestDeciderCount\n return typeof count === 'number' ? count : null\n }\n\n private get reason(): string {\n return this.typed ?? (this.waiting?.reason || '')\n }\n\n /** The tasks that hold the refused privilege, fewest privileges first - the server's order (tasksGranting). */\n private get tasks(): { taskId: string }[] {\n return this.standing.data?.tasksGranting || []\n }\n\n /* A request already waiting keeps the work it named - asking again adds to it and does not change it. */\n private get taskId(): string | undefined {\n return this.chosen ?? this.tasks.find(task => task.taskId === this.waiting?.taskId)?.taskId ?? this.tasks[0]?.taskId\n }\n\n render() {\n return html`\n <div body>\n ${this.renderNeeded()}\n ${this.operationName\n ? html`<div operation>${i18next.t('access-request.text.refused at', { operation: this.operationName })}</div>`\n : ''}\n ${this.waiting\n ? html`<div waiting>\n ${i18next.t('access-request.text.already waiting', { at: new Date(this.waiting.lastRequestedAt).toLocaleString() })}\n </div>`\n : ''}\n ${this.denied\n ? html`<div denied>\n ${i18next.t('access-request.text.denied lately', {\n at: new Date(this.denied.decidedAt).toLocaleDateString(),\n reason: this.denied.decisionReason || ''\n })}\n </div>`\n : ''}\n <md-outlined-text-field\n label=${i18next.t('access-request.field.reason')}\n required\n type=\"textarea\"\n rows=\"2\"\n .value=${this.reason}\n @input=${(e: any) => (this.typed = e.target.value)}\n ></md-outlined-text-field>\n ${this.deciders === null\n ? ''\n : html`<div recorded>\n ${this.deciders > 0\n ? i18next.t('access-request.text.recorded and told', { count: this.deciders })\n : i18next.t('access-request.text.nobody to tell')}\n </div>`}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => history.back()}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.request.pending || !maySend(this.reason, this.denied) || this.deciders === 0} @click=${this.send}>\n ${i18next.t('access-request.button.send')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /** The work the refused privilege belongs to - one line for one task, a choice for several, the privilege for none. */\n private renderNeeded() {\n const t = i18next.t.bind(i18next)\n if (this.tasks.length === 0) return html`<div needed>${privilegeWords(t, this.category, this.privilege)}</div>`\n if (this.tasks.length === 1) {\n const says = taskSays(t, this.tasks[0].taskId)\n return html`<div needed>${taskName(t, this.tasks[0].taskId)}</div>\n ${says ? html`<div says>${says}</div>` : ''}`\n }\n return this.tasks.map(\n task => html`\n <label task>\n <md-radio name=\"task\" .checked=${task.taskId === this.taskId} @change=${() => (this.chosen = task.taskId)}></md-radio>\n <span>\n <div choice>${taskName(t, task.taskId)}</div>\n ${taskSays(t, task.taskId) ? html`<div says>${taskSays(t, task.taskId)}</div>` : ''}\n </span>\n </label>\n `\n )\n }\n\n private async send() {\n const sent = await this.request.run({\n request: {\n category: this.category,\n privilege: this.privilege,\n operationName: this.operationName,\n taskId: this.taskId,\n reason: this.reason.trim()\n }\n })\n\n if (sent) {\n const merged = (sent.requestAccess?.count || 1) > 1\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: { level: 'info', message: i18next.t(merged ? 'access-request.text.added to waiting' : 'access-request.text.sent') }\n })\n )\n history.back()\n }\n }\n}\n"]}
|
|
@@ -30,15 +30,17 @@ declare const AppBindingDetail_base: (new (...args: any[]) => LitElement) & type
|
|
|
30
30
|
export declare class AppBindingDetail extends AppBindingDetail_base {
|
|
31
31
|
static styles: import("lit").CSSResult[];
|
|
32
32
|
appBindingId?: string;
|
|
33
|
-
private binding;
|
|
34
33
|
/** Minted by `renew`, never stored on the record. Empty until someone presses it. */
|
|
35
34
|
private accessToken?;
|
|
36
|
-
|
|
37
|
-
private
|
|
35
|
+
/** The refresh token `renew` handed back with it. */
|
|
36
|
+
private renewedRefreshToken?;
|
|
37
|
+
private read;
|
|
38
|
+
private saves;
|
|
39
|
+
private get binding();
|
|
40
|
+
private get failed();
|
|
41
|
+
private get loadFailed();
|
|
38
42
|
render(): import("lit-html").TemplateResult<1>;
|
|
39
43
|
private expiry;
|
|
40
|
-
connectedCallback(): Promise<void>;
|
|
41
|
-
fetchBinding(): Promise<void>;
|
|
42
44
|
private renewAccessToken;
|
|
43
45
|
private deleteBinding;
|
|
44
46
|
private close;
|
|
@@ -7,7 +7,7 @@ import gql from 'graphql-tag';
|
|
|
7
7
|
import { css, html, LitElement } from 'lit';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
9
|
import { asyncReplace } from 'lit/directives/async-replace.js';
|
|
10
|
-
import {
|
|
10
|
+
import { MutationController, QueryController } from '@operato/graphql';
|
|
11
11
|
import { i18next, localize } from '@operato/i18n';
|
|
12
12
|
import { OxPrompt } from '@operato/popup/ox-prompt.js';
|
|
13
13
|
import { CommonHeaderStyles, CommonPopupStyles, ScrollbarStyles } from '@operato/styles';
|
|
@@ -38,9 +38,42 @@ import './secret-field.js';
|
|
|
38
38
|
let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitElement) {
|
|
39
39
|
constructor() {
|
|
40
40
|
super(...arguments);
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
this.read = new QueryController(this, gql `
|
|
42
|
+
query ($id: String!) {
|
|
43
|
+
appBinding(id: $id) {
|
|
44
|
+
id
|
|
45
|
+
name
|
|
46
|
+
description
|
|
47
|
+
email
|
|
48
|
+
scope
|
|
49
|
+
status
|
|
50
|
+
application {
|
|
51
|
+
id
|
|
52
|
+
name
|
|
53
|
+
description
|
|
54
|
+
email
|
|
55
|
+
icon
|
|
56
|
+
}
|
|
57
|
+
refreshToken
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`, { variables: () => (this.appBindingId ? { id: this.appBindingId } : undefined) });
|
|
61
|
+
/* A refused save: the shared link says why, `failed` says the press landed. */
|
|
62
|
+
this.saves = {
|
|
63
|
+
renew: new MutationController(this, gql `
|
|
64
|
+
mutation ($id: String!) {
|
|
65
|
+
renewApplicationAccessToken(id: $id) {
|
|
66
|
+
accessToken
|
|
67
|
+
refreshToken
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`),
|
|
71
|
+
delete: new MutationController(this, gql `
|
|
72
|
+
mutation ($id: String!) {
|
|
73
|
+
deleteAppBinding(id: $id)
|
|
74
|
+
}
|
|
75
|
+
`)
|
|
76
|
+
};
|
|
44
77
|
}
|
|
45
78
|
/*
|
|
46
79
|
* ── Where the scrolling happens ─────────────────────────────────────────
|
|
@@ -129,6 +162,24 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
129
162
|
}
|
|
130
163
|
`
|
|
131
164
|
]; }
|
|
165
|
+
get binding() {
|
|
166
|
+
const binding = this.read.data?.appBinding;
|
|
167
|
+
if (!binding)
|
|
168
|
+
return null;
|
|
169
|
+
return this.renewedRefreshToken ? { ...binding, refreshToken: this.renewedRefreshToken } : binding;
|
|
170
|
+
}
|
|
171
|
+
get failed() {
|
|
172
|
+
return Object.values(this.saves).some(save => save.error);
|
|
173
|
+
}
|
|
174
|
+
/*
|
|
175
|
+
* `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets
|
|
176
|
+
* the record and an error pointing at that one field. That is not a failed read — the
|
|
177
|
+
* rest of the binding is there and worth showing. Only a refusal of the record itself,
|
|
178
|
+
* which arrives with no data, blanks the popup.
|
|
179
|
+
*/
|
|
180
|
+
get loadFailed() {
|
|
181
|
+
return !this.read.loading && (!!this.read.data || !!this.read.error) && !this.read.data?.appBinding;
|
|
182
|
+
}
|
|
132
183
|
render() {
|
|
133
184
|
if (this.loadFailed) {
|
|
134
185
|
return html `<div class="failure">${i18next.t('text.the binding could not be loaded')}</div>`;
|
|
@@ -224,47 +275,6 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
224
275
|
${asyncReplace(this.expTimer(exp))}
|
|
225
276
|
</div>`;
|
|
226
277
|
}
|
|
227
|
-
async connectedCallback() {
|
|
228
|
-
super.connectedCallback();
|
|
229
|
-
await this.fetchBinding();
|
|
230
|
-
}
|
|
231
|
-
async fetchBinding() {
|
|
232
|
-
const response = await client.query({
|
|
233
|
-
query: gql `
|
|
234
|
-
query ($id: String!) {
|
|
235
|
-
appBinding(id: $id) {
|
|
236
|
-
id
|
|
237
|
-
name
|
|
238
|
-
description
|
|
239
|
-
email
|
|
240
|
-
scope
|
|
241
|
-
status
|
|
242
|
-
application {
|
|
243
|
-
id
|
|
244
|
-
name
|
|
245
|
-
description
|
|
246
|
-
email
|
|
247
|
-
icon
|
|
248
|
-
}
|
|
249
|
-
refreshToken
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
`,
|
|
253
|
-
variables: { id: this.appBindingId }
|
|
254
|
-
});
|
|
255
|
-
/*
|
|
256
|
-
* `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets
|
|
257
|
-
* the record and an error pointing at that one field. That is not a failed read — the
|
|
258
|
-
* rest of the binding is there and worth showing. Only a refusal of the record itself,
|
|
259
|
-
* which arrives with no data, blanks the popup.
|
|
260
|
-
*/
|
|
261
|
-
if (!response.data?.appBinding) {
|
|
262
|
-
this.loadFailed = true;
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
this.loadFailed = false;
|
|
266
|
-
this.binding = response.data.appBinding;
|
|
267
|
-
}
|
|
268
278
|
async renewAccessToken(e) {
|
|
269
279
|
e.preventDefault();
|
|
270
280
|
const confirmed = await OxPrompt.open({
|
|
@@ -277,29 +287,17 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
277
287
|
if (!confirmed) {
|
|
278
288
|
return;
|
|
279
289
|
}
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
mutation ($id: String!) {
|
|
283
|
-
renewApplicationAccessToken(id: $id) {
|
|
284
|
-
accessToken
|
|
285
|
-
refreshToken
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
`,
|
|
289
|
-
variables: { id: this.appBindingId }
|
|
290
|
-
});
|
|
291
|
-
if (response.errors) {
|
|
292
|
-
this.failed = true;
|
|
290
|
+
const renewed = await this.saves.renew.run({ id: this.appBindingId });
|
|
291
|
+
if (!renewed) {
|
|
293
292
|
return;
|
|
294
293
|
}
|
|
295
294
|
/*
|
|
296
295
|
* Stays open. The access token is minted here and stored nowhere — if this popup closed,
|
|
297
296
|
* the value would be gone for good.
|
|
298
297
|
*/
|
|
299
|
-
const { accessToken, refreshToken } =
|
|
300
|
-
this.failed = false;
|
|
298
|
+
const { accessToken, refreshToken } = renewed.renewApplicationAccessToken;
|
|
301
299
|
this.accessToken = accessToken;
|
|
302
|
-
this.
|
|
300
|
+
this.renewedRefreshToken = refreshToken;
|
|
303
301
|
this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }));
|
|
304
302
|
}
|
|
305
303
|
async deleteBinding(e) {
|
|
@@ -314,19 +312,9 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
314
312
|
if (!confirmed) {
|
|
315
313
|
return;
|
|
316
314
|
}
|
|
317
|
-
|
|
318
|
-
mutation: gql `
|
|
319
|
-
mutation ($id: String!) {
|
|
320
|
-
deleteAppBinding(id: $id)
|
|
321
|
-
}
|
|
322
|
-
`,
|
|
323
|
-
variables: { id: this.appBindingId }
|
|
324
|
-
});
|
|
325
|
-
if (response.errors) {
|
|
326
|
-
this.failed = true;
|
|
315
|
+
if (!(await this.saves.delete.run({ id: this.appBindingId }))) {
|
|
327
316
|
return;
|
|
328
317
|
}
|
|
329
|
-
this.failed = false;
|
|
330
318
|
this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }));
|
|
331
319
|
this.close();
|
|
332
320
|
}
|
|
@@ -362,22 +350,14 @@ __decorate([
|
|
|
362
350
|
property({ type: String }),
|
|
363
351
|
__metadata("design:type", String)
|
|
364
352
|
], AppBindingDetail.prototype, "appBindingId", void 0);
|
|
365
|
-
__decorate([
|
|
366
|
-
state(),
|
|
367
|
-
__metadata("design:type", Object)
|
|
368
|
-
], AppBindingDetail.prototype, "binding", void 0);
|
|
369
353
|
__decorate([
|
|
370
354
|
state(),
|
|
371
355
|
__metadata("design:type", String)
|
|
372
356
|
], AppBindingDetail.prototype, "accessToken", void 0);
|
|
373
357
|
__decorate([
|
|
374
358
|
state(),
|
|
375
|
-
__metadata("design:type",
|
|
376
|
-
], AppBindingDetail.prototype, "
|
|
377
|
-
__decorate([
|
|
378
|
-
state(),
|
|
379
|
-
__metadata("design:type", Boolean)
|
|
380
|
-
], AppBindingDetail.prototype, "loadFailed", void 0);
|
|
359
|
+
__metadata("design:type", String)
|
|
360
|
+
], AppBindingDetail.prototype, "renewedRefreshToken", void 0);
|
|
381
361
|
AppBindingDetail = __decorate([
|
|
382
362
|
customElement('app-binding-detail')
|
|
383
363
|
], AppBindingDetail);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-binding-detail.js","sourceRoot":"","sources":["../../client/components/app-binding-detail.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-popup-actions.js'\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, CommonPopupStyles, ScrollbarStyles } from '@operato/styles'\nimport { parseJwt, sleep } from '@operato/utils'\n\nimport './secret-field.js'\n\n/**\n * One application binding, in a popup.\n *\n * ── Read-only, and that is the point ────────────────────────────────────────\n * A binding is not authored here. It is what an application ends up with after it walks the\n * OAuth flow, so there is no create and no edit — only two things a person can do to one:\n * renew its access token, or revoke it by deleting the binding. Everything else on this\n * screen is the record saying what it is.\n *\n * ── Two defects the page this replaced carried ──────────────────────────────\n * 1. **The expiry line said 1970.** A JWT `exp` is in seconds; the page passed it straight to\n * `new Date(...)`, which reads milliseconds. So `new Date(1760000000)` — three weeks after\n * the epoch — was printed as the moment a token expires, beside a countdown computed in\n * seconds that was right. Two numbers for the same instant, disagreeing by half a century.\n * 2. **A field was labelled `name` and showed the email.** `<input .value=${binding.email}>`\n * under `<label>name</label>`.\n *\n * ── Why the access token starts empty ───────────────────────────────────────\n * `AppBinding` stores a refresh token; an access token is minted, handed over, and not kept.\n * So the access-token field has nothing in it until this screen mints a fresh one, and that\n * is the honest state rather than a gap to fill.\n */\n@customElement('app-binding-detail')\nexport class AppBindingDetail extends localize(i18next)(LitElement) {\n /*\n * ── Where the scrolling happens ─────────────────────────────────────────\n * The button row stays put and only the middle scrolls. That is the house shape for a\n * popup, and this had it wrong: the host itself scrolled, so the row you press to finish\n * walked off the bottom of the card along with everything else.\n *\n * So the host clips, and the content area between the title and the buttons is the one\n * thing with overflow. Two details carry it:\n *\n * min-height: 0 A flex item defaults to min-height auto, which refuses to shrink below\n * its own content. Without this the overflow rule sits there doing\n * nothing and the card grows instead — it reads as a working rule.\n * no padding CommonPopupStyles already pads the host. Padding here again doubles the\n * inset and pushes the scrollbar in off the card edge.\n *\n * ScrollbarStyles gives the scrollbar the product's own shape rather than the browser's.\n */\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n /* the host does not scroll; the content area below does. see the note on styles. */\n :host {\n overflow: hidden;\n }\n\n /* min-height:0 is load-bearing — see the note on styles. */\n [body] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n\n display: flex;\n flex-direction: column;\n gap: var(--popup-gap);\n }\n\n fieldset {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: 0;\n padding: var(--popup-padding);\n }\n\n legend {\n padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n /* one labelled box. .row puts two of them side by side; alone it spans the card. */\n [field] {\n display: flex;\n flex-direction: column;\n flex: 1;\n }\n\n label {\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 background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n font: var(--input-font);\n\n opacity: 0.7;\n }\n\n input:focus {\n outline: none;\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: String }) appBindingId?: string\n\n @state() private binding: any = null\n\n /** Minted by `renew`, never stored on the record. Empty until someone presses it. */\n @state() private accessToken?: string\n\n @state() private failed: boolean = false\n\n @state() private loadFailed: boolean = false\n\n render() {\n if (this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the binding could not be loaded')}</div>`\n }\n\n const binding = this.binding || {}\n const app = binding.application || {}\n\n return html`\n <div body>\n <fieldset>\n <legend>${i18next.t('text.application')}</legend>\n <div field>\n <label for=\"app-name\">${i18next.t('field.name')}</label>\n <input id=\"app-name\" type=\"text\" .value=${app.name || ''} readonly />\n </div>\n\n <div field>\n <label for=\"app-description\">${i18next.t('field.description')}</label>\n <input id=\"app-description\" type=\"text\" .value=${app.description || ''} readonly />\n </div>\n\n <div field>\n <label for=\"app-email\">${i18next.t('field.contact email')}</label>\n <input id=\"app-email\" type=\"text\" .value=${app.email || ''} readonly />\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding')}</legend>\n <div field>\n <label for=\"binding-name\">${i18next.t('field.name')}</label>\n <input id=\"binding-name\" type=\"text\" .value=${binding.name || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-email\">${i18next.t('field.email')}</label>\n <input id=\"binding-email\" type=\"text\" .value=${binding.email || ''} readonly />\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"binding-status\">${i18next.t('field.status')}</label>\n <input id=\"binding-status\" type=\"text\" .value=${binding.status || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-scope\">${i18next.t('field.scope')}</label>\n <input id=\"binding-scope\" type=\"text\" .value=${binding.scope || ''} readonly />\n </div>\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding credential')}</legend>\n <div field>\n <label for=\"access-token\">${i18next.t('label.access token')}</label>\n <secret-field id=\"access-token\" .value=${this.accessToken || ''}></secret-field>\n ${this.expiry(this.accessToken)}\n </div>\n\n <div field>\n <label for=\"refresh-token\">${i18next.t('label.refresh token')}</label>\n <secret-field id=\"refresh-token\" .value=${binding.refreshToken || ''}></secret-field>\n ${this.expiry(binding.refreshToken)}\n </div>\n </fieldset>\n\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"start\" destructive @click=${this.deleteBinding.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n <md-text-button slot=\"cancel\" @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>\n <md-filled-button slot=\"confirm\" @click=${this.renewAccessToken.bind(this)}>\n ${i18next.t('button.renew access token')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /*\n * ⚠ A JWT `exp` is **seconds** since the epoch. The page this replaced handed it to\n * `new Date()` unchanged, which reads milliseconds, and printed a date in 1970 next to a\n * countdown that was computed correctly in seconds.\n */\n private expiry(token?: string) {\n const exp = token ? parseJwt(token)?.exp : undefined\n\n if (!exp) {\n return ''\n }\n\n return html`<div class=\"note\">\n ${i18next.t('text.expires at x', { x: new Date(exp * 1000).toLocaleString() })} ·\n ${asyncReplace(this.expTimer(exp))}\n </div>`\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n await this.fetchBinding()\n }\n\n async fetchBinding() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n appBinding(id: $id) {\n id\n name\n description\n email\n scope\n status\n application {\n id\n name\n description\n email\n icon\n }\n refreshToken\n }\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n /*\n * `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets\n * the record and an error pointing at that one field. That is not a failed read — the\n * rest of the binding is there and worth showing. Only a refusal of the record itself,\n * which arrives with no data, blanks the popup.\n */\n if (!response.data?.appBinding) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.binding = response.data.appBinding\n }\n\n private async renewAccessToken(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.renew access token'),\n text: i18next.t('text.the current access token stops working immediately'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n renewApplicationAccessToken(id: $id) {\n accessToken\n refreshToken\n }\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open. The access token is minted here and stored nowhere — if this popup closed,\n * the value would be gone for good.\n */\n const { accessToken, refreshToken } = response.data.renewApplicationAccessToken\n\n this.failed = false\n this.accessToken = accessToken\n this.binding = { ...this.binding, refreshToken }\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteBinding(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.are you sure you want to delete x', { x: this.binding?.application?.name || '' }),\n confirmButton: { text: i18next.t('button.delete') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteAppBinding(id: $id)\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.failed = false\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n this.close()\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n }\n\n private async *expTimer(exp: number) {\n const DAY = 24 * 60 * 60\n const HOUR = 60 * 60\n const MIN = 60\n\n while (this.isConnected) {\n let remain = Math.floor(Number(exp) - Date.now() / 1000)\n\n /*\n * Past the expiry the arithmetic below produces \"-5 days -3 hours\", which reads as a\n * duration rather than as the token being dead. Say it plainly and stop counting.\n */\n if (remain <= 0) {\n yield i18next.t('text.expired')\n return\n }\n\n const days = Math.floor(remain / DAY)\n remain -= days * DAY\n const hours = Math.floor(remain / HOUR)\n remain -= hours * HOUR\n const mins = Math.floor(remain / MIN)\n const secs = remain - mins * MIN\n\n yield i18next.t('text.remaining time', { days, hours, mins, secs })\n\n await sleep(1000)\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"app-binding-detail.js","sourceRoot":"","sources":["../../client/components/app-binding-detail.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-popup-actions.js'\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, CommonPopupStyles, ScrollbarStyles } from '@operato/styles'\nimport { parseJwt, sleep } from '@operato/utils'\n\nimport './secret-field.js'\n\n/**\n * One application binding, in a popup.\n *\n * ── Read-only, and that is the point ────────────────────────────────────────\n * A binding is not authored here. It is what an application ends up with after it walks the\n * OAuth flow, so there is no create and no edit — only two things a person can do to one:\n * renew its access token, or revoke it by deleting the binding. Everything else on this\n * screen is the record saying what it is.\n *\n * ── Two defects the page this replaced carried ──────────────────────────────\n * 1. **The expiry line said 1970.** A JWT `exp` is in seconds; the page passed it straight to\n * `new Date(...)`, which reads milliseconds. So `new Date(1760000000)` — three weeks after\n * the epoch — was printed as the moment a token expires, beside a countdown computed in\n * seconds that was right. Two numbers for the same instant, disagreeing by half a century.\n * 2. **A field was labelled `name` and showed the email.** `<input .value=${binding.email}>`\n * under `<label>name</label>`.\n *\n * ── Why the access token starts empty ───────────────────────────────────────\n * `AppBinding` stores a refresh token; an access token is minted, handed over, and not kept.\n * So the access-token field has nothing in it until this screen mints a fresh one, and that\n * is the honest state rather than a gap to fill.\n */\n@customElement('app-binding-detail')\nexport class AppBindingDetail extends localize(i18next)(LitElement) {\n /*\n * ── Where the scrolling happens ─────────────────────────────────────────\n * The button row stays put and only the middle scrolls. That is the house shape for a\n * popup, and this had it wrong: the host itself scrolled, so the row you press to finish\n * walked off the bottom of the card along with everything else.\n *\n * So the host clips, and the content area between the title and the buttons is the one\n * thing with overflow. Two details carry it:\n *\n * min-height: 0 A flex item defaults to min-height auto, which refuses to shrink below\n * its own content. Without this the overflow rule sits there doing\n * nothing and the card grows instead — it reads as a working rule.\n * no padding CommonPopupStyles already pads the host. Padding here again doubles the\n * inset and pushes the scrollbar in off the card edge.\n *\n * ScrollbarStyles gives the scrollbar the product's own shape rather than the browser's.\n */\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n /* the host does not scroll; the content area below does. see the note on styles. */\n :host {\n overflow: hidden;\n }\n\n /* min-height:0 is load-bearing — see the note on styles. */\n [body] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n\n display: flex;\n flex-direction: column;\n gap: var(--popup-gap);\n }\n\n fieldset {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: 0;\n padding: var(--popup-padding);\n }\n\n legend {\n padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n /* one labelled box. .row puts two of them side by side; alone it spans the card. */\n [field] {\n display: flex;\n flex-direction: column;\n flex: 1;\n }\n\n label {\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 background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n font: var(--input-font);\n\n opacity: 0.7;\n }\n\n input:focus {\n outline: none;\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: String }) appBindingId?: string\n\n /** Minted by `renew`, never stored on the record. Empty until someone presses it. */\n @state() private accessToken?: string\n /** The refresh token `renew` handed back with it. */\n @state() private renewedRefreshToken?: string\n\n private read = new QueryController(\n this,\n gql`\n query ($id: String!) {\n appBinding(id: $id) {\n id\n name\n description\n email\n scope\n status\n application {\n id\n name\n description\n email\n icon\n }\n refreshToken\n }\n }\n `,\n { variables: () => (this.appBindingId ? { id: this.appBindingId } : undefined) }\n )\n\n /* A refused save: the shared link says why, `failed` says the press landed. */\n private saves = {\n renew: new MutationController(\n this,\n gql`\n mutation ($id: String!) {\n renewApplicationAccessToken(id: $id) {\n accessToken\n refreshToken\n }\n }\n `\n ),\n delete: new MutationController(\n this,\n gql`\n mutation ($id: String!) {\n deleteAppBinding(id: $id)\n }\n `\n )\n }\n\n private get binding(): any {\n const binding = this.read.data?.appBinding\n if (!binding) return null\n return this.renewedRefreshToken ? { ...binding, refreshToken: this.renewedRefreshToken } : binding\n }\n\n private get failed(): boolean {\n return Object.values(this.saves).some(save => save.error)\n }\n\n /*\n * `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets\n * the record and an error pointing at that one field. That is not a failed read — the\n * rest of the binding is there and worth showing. Only a refusal of the record itself,\n * which arrives with no data, blanks the popup.\n */\n private get loadFailed(): boolean {\n return !this.read.loading && (!!this.read.data || !!this.read.error) && !this.read.data?.appBinding\n }\n\n render() {\n if (this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the binding could not be loaded')}</div>`\n }\n\n const binding = this.binding || {}\n const app = binding.application || {}\n\n return html`\n <div body>\n <fieldset>\n <legend>${i18next.t('text.application')}</legend>\n <div field>\n <label for=\"app-name\">${i18next.t('field.name')}</label>\n <input id=\"app-name\" type=\"text\" .value=${app.name || ''} readonly />\n </div>\n\n <div field>\n <label for=\"app-description\">${i18next.t('field.description')}</label>\n <input id=\"app-description\" type=\"text\" .value=${app.description || ''} readonly />\n </div>\n\n <div field>\n <label for=\"app-email\">${i18next.t('field.contact email')}</label>\n <input id=\"app-email\" type=\"text\" .value=${app.email || ''} readonly />\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding')}</legend>\n <div field>\n <label for=\"binding-name\">${i18next.t('field.name')}</label>\n <input id=\"binding-name\" type=\"text\" .value=${binding.name || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-email\">${i18next.t('field.email')}</label>\n <input id=\"binding-email\" type=\"text\" .value=${binding.email || ''} readonly />\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"binding-status\">${i18next.t('field.status')}</label>\n <input id=\"binding-status\" type=\"text\" .value=${binding.status || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-scope\">${i18next.t('field.scope')}</label>\n <input id=\"binding-scope\" type=\"text\" .value=${binding.scope || ''} readonly />\n </div>\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding credential')}</legend>\n <div field>\n <label for=\"access-token\">${i18next.t('label.access token')}</label>\n <secret-field id=\"access-token\" .value=${this.accessToken || ''}></secret-field>\n ${this.expiry(this.accessToken)}\n </div>\n\n <div field>\n <label for=\"refresh-token\">${i18next.t('label.refresh token')}</label>\n <secret-field id=\"refresh-token\" .value=${binding.refreshToken || ''}></secret-field>\n ${this.expiry(binding.refreshToken)}\n </div>\n </fieldset>\n\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"start\" destructive @click=${this.deleteBinding.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n <md-text-button slot=\"cancel\" @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>\n <md-filled-button slot=\"confirm\" @click=${this.renewAccessToken.bind(this)}>\n ${i18next.t('button.renew access token')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /*\n * ⚠ A JWT `exp` is **seconds** since the epoch. The page this replaced handed it to\n * `new Date()` unchanged, which reads milliseconds, and printed a date in 1970 next to a\n * countdown that was computed correctly in seconds.\n */\n private expiry(token?: string) {\n const exp = token ? parseJwt(token)?.exp : undefined\n\n if (!exp) {\n return ''\n }\n\n return html`<div class=\"note\">\n ${i18next.t('text.expires at x', { x: new Date(exp * 1000).toLocaleString() })} ·\n ${asyncReplace(this.expTimer(exp))}\n </div>`\n }\n\n private async renewAccessToken(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.renew access token'),\n text: i18next.t('text.the current access token stops working immediately'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const renewed = await this.saves.renew.run({ id: this.appBindingId })\n\n if (!renewed) {\n return\n }\n\n /*\n * Stays open. The access token is minted here and stored nowhere — if this popup closed,\n * the value would be gone for good.\n */\n const { accessToken, refreshToken } = renewed.renewApplicationAccessToken\n\n this.accessToken = accessToken\n this.renewedRefreshToken = refreshToken\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteBinding(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.are you sure you want to delete x', { x: this.binding?.application?.name || '' }),\n confirmButton: { text: i18next.t('button.delete') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n if (!(await this.saves.delete.run({ id: this.appBindingId }))) {\n return\n }\n\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n this.close()\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n }\n\n private async *expTimer(exp: number) {\n const DAY = 24 * 60 * 60\n const HOUR = 60 * 60\n const MIN = 60\n\n while (this.isConnected) {\n let remain = Math.floor(Number(exp) - Date.now() / 1000)\n\n /*\n * Past the expiry the arithmetic below produces \"-5 days -3 hours\", which reads as a\n * duration rather than as the token being dead. Say it plainly and stop counting.\n */\n if (remain <= 0) {\n yield i18next.t('text.expired')\n return\n }\n\n const days = Math.floor(remain / DAY)\n remain -= days * DAY\n const hours = Math.floor(remain / HOUR)\n remain -= hours * HOUR\n const mins = Math.floor(remain / MIN)\n const secs = remain - mins * MIN\n\n yield i18next.t('text.remaining time', { days, hours, mins, secs })\n\n await sleep(1000)\n }\n }\n}\n"]}
|
|
@@ -10,22 +10,23 @@ export declare class ApplianceEditor extends ApplianceEditor_base {
|
|
|
10
10
|
/** The appliance to edit. Absent means this popup is registering a new one. */
|
|
11
11
|
applianceId?: string;
|
|
12
12
|
form: HTMLFormElement;
|
|
13
|
-
/** The loaded record. The patch is a diff against this, so it is never rebuilt from the form. */
|
|
14
|
-
private appliance;
|
|
15
13
|
/** Required fields still empty. Filled in when the person presses save, not while typing. */
|
|
16
14
|
private missing;
|
|
15
|
+
/** The record as a new secret or a revocation left it - the panel shows the result and stays open. */
|
|
16
|
+
private changed;
|
|
17
|
+
private read;
|
|
18
|
+
private revocation;
|
|
19
|
+
private saves;
|
|
20
|
+
/** The loaded record. The patch is a diff against this, so it is never rebuilt from the form. */
|
|
21
|
+
private get appliance();
|
|
17
22
|
/** The mutation was refused. The shared link already said why; this says the press landed. */
|
|
18
|
-
private failed;
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
private revocable;
|
|
23
|
+
private get failed();
|
|
24
|
+
private get loadFailed();
|
|
25
|
+
private get revocable();
|
|
22
26
|
private get editing();
|
|
23
27
|
render(): import("lit-html").TemplateResult<1>;
|
|
24
28
|
private renderCredential;
|
|
25
29
|
private fieldError;
|
|
26
|
-
connectedCallback(): Promise<void>;
|
|
27
|
-
private fetchRevocable;
|
|
28
|
-
fetchAppliance(): Promise<void>;
|
|
29
30
|
private cancel;
|
|
30
31
|
save(e: Event): Promise<void>;
|
|
31
32
|
private createAppliance;
|