@things-factory/auth-ui 10.1.34 → 10.1.38
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.js +6 -2
- package/dist-client/components/accept-invitation.js.map +1 -1
- package/dist-client/components/app-binding-detail.d.ts +1 -0
- package/dist-client/components/app-binding-detail.js +6 -9
- package/dist-client/components/app-binding-detail.js.map +1 -1
- package/dist-client/components/appliance-editor.d.ts +1 -0
- package/dist-client/components/appliance-editor.js +6 -10
- package/dist-client/components/appliance-editor.js.map +1 -1
- package/dist-client/components/application-editor.d.ts +1 -0
- package/dist-client/components/application-editor.js +6 -9
- package/dist-client/components/application-editor.js.map +1 -1
- package/dist-client/components/popup-actions-row.test.d.ts +1 -0
- package/dist-client/components/popup-actions-row.test.js +36 -0
- package/dist-client/components/popup-actions-row.test.js.map +1 -0
- package/dist-client/pages/app-binding/app-bindings.d.ts +1 -1
- package/dist-client/pages/app-binding/app-bindings.js +5 -4
- package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
- package/dist-client/pages/appliance/home.d.ts +1 -1
- package/dist-client/pages/appliance/home.js +5 -4
- package/dist-client/pages/appliance/home.js.map +1 -1
- package/dist-client/pages/application/applications.d.ts +1 -1
- package/dist-client/pages/application/applications.js +5 -4
- package/dist-client/pages/application/applications.js.map +1 -1
- package/dist-client/pages/invitation/invitations.js +2 -0
- package/dist-client/pages/invitation/invitations.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/translations/en.json +0 -1
- package/translations/ja.json +21 -21
- package/translations/ko.json +31 -32
- package/translations/zh.json +19 -19
|
@@ -59,8 +59,12 @@ export class AcceptInvitation extends AbstractAuthPage {
|
|
|
59
59
|
get pageName() {
|
|
60
60
|
return 'accept invitation';
|
|
61
61
|
}
|
|
62
|
+
/*
|
|
63
|
+
* ⚠ `/auth/…` — the server router carries that prefix. This read `/accept-invitation` until
|
|
64
|
+
* 2026-09-24, which is no route; see the note beside `ACCEPT_INVITATION_PATH` in auth-base.
|
|
65
|
+
*/
|
|
62
66
|
get actionUrl() {
|
|
63
|
-
return '/accept-invitation';
|
|
67
|
+
return '/auth/accept-invitation';
|
|
64
68
|
}
|
|
65
69
|
get title() {
|
|
66
70
|
return 'accept invitation';
|
|
@@ -201,7 +205,7 @@ export class AcceptInvitation extends AbstractAuthPage {
|
|
|
201
205
|
this.busy = true;
|
|
202
206
|
this.message = '';
|
|
203
207
|
try {
|
|
204
|
-
const response = await fetch(
|
|
208
|
+
const response = await fetch(this.actionUrl, {
|
|
205
209
|
method: 'POST',
|
|
206
210
|
headers: { 'Content-Type': 'application/json' },
|
|
207
211
|
body: JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accept-invitation.js","sourceRoot":"","sources":["../../client/components/accept-invitation.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@material/web/textfield/filled-text-field.js'\n\nimport '@operato/i18n/ox-i18n.js'\nimport '@operato/i18n/ox-i18n-selector.js'\nimport '@operato/layout/ox-snack-bar.js'\n\nimport { css, html, nothing } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { AUTH_STYLE_SIGN } from '../auth-style-sign.js'\nimport { EntryControlSizeStyles } from '../entry-control-size-styles.js'\nimport { AbstractAuthPage } from './abstract-auth-page.js'\nimport { readInvitationToken } from '../utils/invitation-token.js'\n\n/**\n * Accepting an invitation.\n *\n * ── Why the token is not in the address ─────────────────────────────────────\n * The link is `…/accept-invitation#<token>`, and a browser does not send what follows the `#`.\n * So the request that loads this page carries nothing, and the token stays out of server access\n * logs, proxy logs and `Referer`. This page reads it from the fragment and then clears it from\n * the address bar, so it does not sit in the browser's history either.\n *\n * The one place it can still travel is the mail being forwarded, and nothing about where the\n * token sits can prevent that. So the address it was sent to is asked for as well: holding the\n * link is not enough on its own.\n *\n * ── Why the password fields only appear after a try ─────────────────────────\n * Whether an address already has an account is not this page's to announce. It sends what it\n * has, and the server answers `invitation-needs-a-password` when it needs one — so the extra\n * fields appear for the person who is actually creating an account, and nobody learns anything\n * by loading the page.\n */\nexport class AcceptInvitation extends AbstractAuthPage {\n static styles = [\n EntryControlSizeStyles,\n css`\n :host {\n position: relative;\n overflow: hidden;\n\n display: flex;\n flex-direction: row;\n\n width: 100vw;\n height: 100vh;\n height: 100dvh;\n }\n\n .note {\n margin: 0 0 var(--spacing-medium) 0;\n font-size: 0.85rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n `,\n AUTH_STYLE_SIGN\n ]\n\n get pageName() {\n return 'accept invitation'\n }\n\n get actionUrl() {\n return '/accept-invitation'\n }\n\n get title() {\n return 'accept invitation'\n }\n\n /** Read from the fragment on connect. Empty when the link was opened without one. */\n @property({ type: String }) token?: string\n\n /** The server asked for a password, so this person is creating an account. */\n @property({ type: Boolean }) needsPassword?: boolean\n\n /** Joined. The page stops being a form and says where to go. */\n @property({ type: Boolean }) joined?: boolean\n\n @property({ type: Boolean }) busy?: boolean\n\n connectedCallback() {\n super.connectedCallback()\n\n const { token, clean } = readInvitationToken(window.location.href)\n\n this.token = token\n\n /*\n * Taken out of the address bar and out of this history entry. `replaceState` rather than\n * `pushState`: going back should not put the token back on screen.\n */\n if (clean && clean !== window.location.href) {\n window.history.replaceState(null, '', clean)\n }\n }\n\n render() {\n const { icon, title, description } = this.applicationMeta\n const { disableUserFavoredLanguage, languages } = this.data || {}\n\n return html`\n <div class=\"wrap\">\n <div class=\"auth-brand\">\n <img src=${icon} />\n <strong class=\"name\">${title}</strong>\n <span class=\"welcome-msg\">${description}</span>\n </div>\n\n <div class=\"auth-form\">\n <h3><ox-i18n msgid=\"title.accept invitation\"></ox-i18n></h3>\n\n ${this.joined ? this.renderDone() : this.renderForm(disableUserFavoredLanguage, languages)}\n </div>\n </div>\n\n <ox-snack-bar id=\"snackbar\" level=\"error\" .message=${this.message}></ox-snack-bar>\n `\n }\n\n private renderDone() {\n return html`\n <p class=\"note\">${i18next.t('text.you have joined. sign in to continue')}</p>\n <md-elevated-button @click=${() => (window.location.href = '/auth/signin')}>\n <ox-i18n msgid=\"button.sign in\"></ox-i18n>\n </md-elevated-button>\n `\n }\n\n private renderForm(disableUserFavoredLanguage: boolean, languages: any) {\n return html`\n <form id=\"form\" @keypress=${(e: KeyboardEvent) => e.key === 'Enter' && this.accept()}>\n <p class=\"note\">${i18next.t('text.confirm the address this invitation was sent to')}</p>\n\n <div class=\"field\">\n <md-filled-text-field\n name=\"email\"\n type=\"email\"\n label=${String(i18next.t('field.email'))}\n autocomplete=\"email\"\n required\n ><md-icon slot=\"leading-icon\">mail</md-icon></md-filled-text-field\n >\n </div>\n\n ${this.token\n ? nothing\n : html`\n <div class=\"field\">\n <md-filled-text-field\n name=\"token\"\n label=${String(i18next.t('label.invitation code'))}\n supporting-text=${String(i18next.t('text.the code is printed in the invitation email'))}\n autocomplete=\"off\"\n required\n ><md-icon slot=\"leading-icon\">key</md-icon></md-filled-text-field\n >\n </div>\n `}\n ${this.needsPassword ? this.renderNewAccount() : nothing}\n\n <md-elevated-button id=\"submit-button\" type=\"button\" ?disabled=${this.busy} @click=${() => this.accept()}>\n <ox-i18n msgid=\"button.accept invitation\"></ox-i18n>\n </md-elevated-button>\n\n ${!disableUserFavoredLanguage\n ? html`<div id=\"locale-area\">\n <label for=\"locale-selector\"><md-icon>language</md-icon></label>\n <ox-i18n-selector\n id=\"locale-selector\"\n value=${i18next.language || 'en-US'}\n .languages=${languages}\n @change=${(e: CustomEvent) => e.detail && i18next.changeLanguage(e.detail as any)}\n ></ox-i18n-selector>\n </div>`\n : nothing}\n </form>\n `\n }\n\n private renderNewAccount() {\n return html`\n <p class=\"note\">${i18next.t('text.choose a password to finish joining')}</p>\n\n <div class=\"field\">\n <md-filled-text-field name=\"name\" label=${String(i18next.t('field.name'))} autocomplete=\"name\"\n ><md-icon slot=\"leading-icon\">person</md-icon></md-filled-text-field\n >\n </div>\n\n <div class=\"field\">\n <md-filled-text-field\n name=\"password\"\n type=\"password\"\n label=${String(i18next.t('label.password'))}\n autocomplete=\"new-password\"\n required\n ><md-icon slot=\"leading-icon\">password</md-icon></md-filled-text-field\n >\n </div>\n `\n }\n\n /*\n * ⚠ Not called `valueOf`. Overriding `Object.prototype.valueOf` with a different signature\n * makes the class type unassignable to `Object`, and every `@property` decorator on it then\n * fails to resolve — with an error that talks about decorators and never mentions the method.\n */\n private fieldValue(name: string): string {\n return (this.renderRoot.querySelector(`[name=\"${name}\"]`) as any)?.value?.trim() || ''\n }\n\n /*\n * The base class calls this to commit the page. It does not submit the form element: the\n * answer here is read rather than navigated to, because a refusal can mean \"now ask for a\n * password\" instead of \"stop\".\n */\n async submit() {\n await this.accept()\n }\n\n private async accept() {\n if (this.busy) {\n return\n }\n\n this.busy = true\n this.message = ''\n\n try {\n const response = await fetch('/accept-invitation', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n token: this.token || this.fieldValue('token'),\n email: this.fieldValue('email'),\n name: this.fieldValue('name'),\n password: this.fieldValue('password')\n })\n })\n\n if (response.ok) {\n this.joined = true\n return\n }\n\n const refusal = await response.json().catch(() => ({}))\n\n /*\n * The one refusal that is not the end of the attempt — it means this person has no account\n * yet, which is exactly who this page is for.\n */\n if (refusal?.refusal?.code === 'invitation-needs-a-password' || refusal?.code === 'invitation-needs-a-password') {\n this.needsPassword = true\n this.message = String(i18next.t('text.choose a password to finish joining'))\n return\n }\n\n this.message = refusal?.message || String(i18next.t('text.this invitation cannot be used'))\n } catch (e) {\n this.message = String(i18next.t('text.this invitation cannot be used'))\n } finally {\n this.busy = false\n }\n }\n\n updated(changed: any) {\n super.updated(changed)\n\n if (changed.has('message')) {\n if (!this.message) {\n this.hideSnackbar()\n } else {\n this.showSnackbar({ timer: -1 })\n }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"accept-invitation.js","sourceRoot":"","sources":["../../client/components/accept-invitation.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@material/web/textfield/filled-text-field.js'\n\nimport '@operato/i18n/ox-i18n.js'\nimport '@operato/i18n/ox-i18n-selector.js'\nimport '@operato/layout/ox-snack-bar.js'\n\nimport { css, html, nothing } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { AUTH_STYLE_SIGN } from '../auth-style-sign.js'\nimport { EntryControlSizeStyles } from '../entry-control-size-styles.js'\nimport { AbstractAuthPage } from './abstract-auth-page.js'\nimport { readInvitationToken } from '../utils/invitation-token.js'\n\n/**\n * Accepting an invitation.\n *\n * ── Why the token is not in the address ─────────────────────────────────────\n * The link is `…/accept-invitation#<token>`, and a browser does not send what follows the `#`.\n * So the request that loads this page carries nothing, and the token stays out of server access\n * logs, proxy logs and `Referer`. This page reads it from the fragment and then clears it from\n * the address bar, so it does not sit in the browser's history either.\n *\n * The one place it can still travel is the mail being forwarded, and nothing about where the\n * token sits can prevent that. So the address it was sent to is asked for as well: holding the\n * link is not enough on its own.\n *\n * ── Why the password fields only appear after a try ─────────────────────────\n * Whether an address already has an account is not this page's to announce. It sends what it\n * has, and the server answers `invitation-needs-a-password` when it needs one — so the extra\n * fields appear for the person who is actually creating an account, and nobody learns anything\n * by loading the page.\n */\nexport class AcceptInvitation extends AbstractAuthPage {\n static styles = [\n EntryControlSizeStyles,\n css`\n :host {\n position: relative;\n overflow: hidden;\n\n display: flex;\n flex-direction: row;\n\n width: 100vw;\n height: 100vh;\n height: 100dvh;\n }\n\n .note {\n margin: 0 0 var(--spacing-medium) 0;\n font-size: 0.85rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n `,\n AUTH_STYLE_SIGN\n ]\n\n get pageName() {\n return 'accept invitation'\n }\n\n /*\n * ⚠ `/auth/…` — the server router carries that prefix. This read `/accept-invitation` until\n * 2026-09-24, which is no route; see the note beside `ACCEPT_INVITATION_PATH` in auth-base.\n */\n get actionUrl() {\n return '/auth/accept-invitation'\n }\n\n get title() {\n return 'accept invitation'\n }\n\n /** Read from the fragment on connect. Empty when the link was opened without one. */\n @property({ type: String }) token?: string\n\n /** The server asked for a password, so this person is creating an account. */\n @property({ type: Boolean }) needsPassword?: boolean\n\n /** Joined. The page stops being a form and says where to go. */\n @property({ type: Boolean }) joined?: boolean\n\n @property({ type: Boolean }) busy?: boolean\n\n connectedCallback() {\n super.connectedCallback()\n\n const { token, clean } = readInvitationToken(window.location.href)\n\n this.token = token\n\n /*\n * Taken out of the address bar and out of this history entry. `replaceState` rather than\n * `pushState`: going back should not put the token back on screen.\n */\n if (clean && clean !== window.location.href) {\n window.history.replaceState(null, '', clean)\n }\n }\n\n render() {\n const { icon, title, description } = this.applicationMeta\n const { disableUserFavoredLanguage, languages } = this.data || {}\n\n return html`\n <div class=\"wrap\">\n <div class=\"auth-brand\">\n <img src=${icon} />\n <strong class=\"name\">${title}</strong>\n <span class=\"welcome-msg\">${description}</span>\n </div>\n\n <div class=\"auth-form\">\n <h3><ox-i18n msgid=\"title.accept invitation\"></ox-i18n></h3>\n\n ${this.joined ? this.renderDone() : this.renderForm(disableUserFavoredLanguage, languages)}\n </div>\n </div>\n\n <ox-snack-bar id=\"snackbar\" level=\"error\" .message=${this.message}></ox-snack-bar>\n `\n }\n\n private renderDone() {\n return html`\n <p class=\"note\">${i18next.t('text.you have joined. sign in to continue')}</p>\n <md-elevated-button @click=${() => (window.location.href = '/auth/signin')}>\n <ox-i18n msgid=\"button.sign in\"></ox-i18n>\n </md-elevated-button>\n `\n }\n\n private renderForm(disableUserFavoredLanguage: boolean, languages: any) {\n return html`\n <form id=\"form\" @keypress=${(e: KeyboardEvent) => e.key === 'Enter' && this.accept()}>\n <p class=\"note\">${i18next.t('text.confirm the address this invitation was sent to')}</p>\n\n <div class=\"field\">\n <md-filled-text-field\n name=\"email\"\n type=\"email\"\n label=${String(i18next.t('field.email'))}\n autocomplete=\"email\"\n required\n ><md-icon slot=\"leading-icon\">mail</md-icon></md-filled-text-field\n >\n </div>\n\n ${this.token\n ? nothing\n : html`\n <div class=\"field\">\n <md-filled-text-field\n name=\"token\"\n label=${String(i18next.t('label.invitation code'))}\n supporting-text=${String(i18next.t('text.the code is printed in the invitation email'))}\n autocomplete=\"off\"\n required\n ><md-icon slot=\"leading-icon\">key</md-icon></md-filled-text-field\n >\n </div>\n `}\n ${this.needsPassword ? this.renderNewAccount() : nothing}\n\n <md-elevated-button id=\"submit-button\" type=\"button\" ?disabled=${this.busy} @click=${() => this.accept()}>\n <ox-i18n msgid=\"button.accept invitation\"></ox-i18n>\n </md-elevated-button>\n\n ${!disableUserFavoredLanguage\n ? html`<div id=\"locale-area\">\n <label for=\"locale-selector\"><md-icon>language</md-icon></label>\n <ox-i18n-selector\n id=\"locale-selector\"\n value=${i18next.language || 'en-US'}\n .languages=${languages}\n @change=${(e: CustomEvent) => e.detail && i18next.changeLanguage(e.detail as any)}\n ></ox-i18n-selector>\n </div>`\n : nothing}\n </form>\n `\n }\n\n private renderNewAccount() {\n return html`\n <p class=\"note\">${i18next.t('text.choose a password to finish joining')}</p>\n\n <div class=\"field\">\n <md-filled-text-field name=\"name\" label=${String(i18next.t('field.name'))} autocomplete=\"name\"\n ><md-icon slot=\"leading-icon\">person</md-icon></md-filled-text-field\n >\n </div>\n\n <div class=\"field\">\n <md-filled-text-field\n name=\"password\"\n type=\"password\"\n label=${String(i18next.t('label.password'))}\n autocomplete=\"new-password\"\n required\n ><md-icon slot=\"leading-icon\">password</md-icon></md-filled-text-field\n >\n </div>\n `\n }\n\n /*\n * ⚠ Not called `valueOf`. Overriding `Object.prototype.valueOf` with a different signature\n * makes the class type unassignable to `Object`, and every `@property` decorator on it then\n * fails to resolve — with an error that talks about decorators and never mentions the method.\n */\n private fieldValue(name: string): string {\n return (this.renderRoot.querySelector(`[name=\"${name}\"]`) as any)?.value?.trim() || ''\n }\n\n /*\n * The base class calls this to commit the page. It does not submit the form element: the\n * answer here is read rather than navigated to, because a refusal can mean \"now ask for a\n * password\" instead of \"stop\".\n */\n async submit() {\n await this.accept()\n }\n\n private async accept() {\n if (this.busy) {\n return\n }\n\n this.busy = true\n this.message = ''\n\n try {\n const response = await fetch(this.actionUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n token: this.token || this.fieldValue('token'),\n email: this.fieldValue('email'),\n name: this.fieldValue('name'),\n password: this.fieldValue('password')\n })\n })\n\n if (response.ok) {\n this.joined = true\n return\n }\n\n const refusal = await response.json().catch(() => ({}))\n\n /*\n * The one refusal that is not the end of the attempt — it means this person has no account\n * yet, which is exactly who this page is for.\n */\n if (refusal?.refusal?.code === 'invitation-needs-a-password' || refusal?.code === 'invitation-needs-a-password') {\n this.needsPassword = true\n this.message = String(i18next.t('text.choose a password to finish joining'))\n return\n }\n\n this.message = refusal?.message || String(i18next.t('text.this invitation cannot be used'))\n } catch (e) {\n this.message = String(i18next.t('text.this invitation cannot be used'))\n } finally {\n this.busy = false\n }\n }\n\n updated(changed: any) {\n super.updated(changed)\n\n if (changed.has('message')) {\n if (!this.message) {\n this.hideSnackbar()\n } else {\n this.showSnackbar({ timer: -1 })\n }\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
2
3
|
import '@material/web/icon/icon.js';
|
|
3
4
|
import '@material/web/button/text-button.js';
|
|
4
5
|
import '@material/web/button/filled-button.js';
|
|
@@ -121,10 +122,6 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
121
122
|
outline: none;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
.buttons .left {
|
|
125
|
-
margin-right: auto;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
125
|
@media screen and (max-width: 480px) {
|
|
129
126
|
[field] {
|
|
130
127
|
grid-column: span 2;
|
|
@@ -201,15 +198,15 @@ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitEleme
|
|
|
201
198
|
${this.failed ? html `<div class="failure">${i18next.t('text.could not be saved')}</div>` : ''}
|
|
202
199
|
</div>
|
|
203
200
|
|
|
204
|
-
<
|
|
205
|
-
<md-text-button
|
|
201
|
+
<ox-popup-actions>
|
|
202
|
+
<md-text-button slot="start" destructive @click=${this.deleteBinding.bind(this)}>
|
|
206
203
|
${i18next.t('button.delete')}
|
|
207
204
|
</md-text-button>
|
|
208
|
-
<md-text-button @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>
|
|
209
|
-
<md-filled-button @click=${this.renewAccessToken.bind(this)}>
|
|
205
|
+
<md-text-button slot="cancel" @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>
|
|
206
|
+
<md-filled-button slot="confirm" @click=${this.renewAccessToken.bind(this)}>
|
|
210
207
|
${i18next.t('button.renew access token')}
|
|
211
208
|
</md-filled-button>
|
|
212
|
-
</
|
|
209
|
+
</ox-popup-actions>
|
|
213
210
|
`;
|
|
214
211
|
}
|
|
215
212
|
/*
|
|
@@ -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 '@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 .buttons .left {\n margin-right: auto;\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 <div class=\"buttons\">\n <md-text-button class=\"left\" @click=${this.deleteBinding.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n <md-text-button @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>\n <md-filled-button @click=${this.renewAccessToken.bind(this)}>\n ${i18next.t('button.renew access token')}\n </md-filled-button>\n </div>\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 { 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,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
2
3
|
import '@material/web/icon/icon.js';
|
|
3
4
|
import '@material/web/button/text-button.js';
|
|
4
5
|
import '@material/web/button/filled-button.js';
|
|
@@ -170,11 +171,6 @@ let ApplianceEditor = class ApplianceEditor extends localize(i18next)(LitElement
|
|
|
170
171
|
margin-top: var(--spacing-tiny, 2px);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
/* The one reverting action goes left, away from the ones that commit. */
|
|
174
|
-
.buttons .left {
|
|
175
|
-
margin-right: auto;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
174
|
/* taking a credential away is not the same kind of act as issuing one. */
|
|
179
175
|
md-text-button.danger {
|
|
180
176
|
--md-text-button-label-text-color: var(--md-sys-color-error);
|
|
@@ -276,19 +272,19 @@ let ApplianceEditor = class ApplianceEditor extends localize(i18next)(LitElement
|
|
|
276
272
|
${this.failed ? html `<div class="failure">${i18next.t('text.could not be saved')}</div>` : ''}
|
|
277
273
|
</form>
|
|
278
274
|
|
|
279
|
-
<
|
|
275
|
+
<ox-popup-actions>
|
|
280
276
|
${this.editing
|
|
281
277
|
? html `
|
|
282
|
-
<md-text-button
|
|
278
|
+
<md-text-button slot="start" destructive @click=${this.deleteAppliance.bind(this)}>
|
|
283
279
|
${i18next.t('button.delete')}
|
|
284
280
|
</md-text-button>
|
|
285
281
|
`
|
|
286
282
|
: ''}
|
|
287
|
-
<md-text-button @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>
|
|
288
|
-
<md-filled-button @click=${this.save.bind(this)}>
|
|
283
|
+
<md-text-button slot="cancel" @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>
|
|
284
|
+
<md-filled-button slot="confirm" @click=${this.save.bind(this)}>
|
|
289
285
|
${this.editing ? i18next.t('button.update') : i18next.t('button.register')}
|
|
290
286
|
</md-filled-button>
|
|
291
|
-
</
|
|
287
|
+
</ox-popup-actions>
|
|
292
288
|
`;
|
|
293
289
|
}
|
|
294
290
|
/*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appliance-editor.js","sourceRoot":"","sources":["../../client/components/appliance-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@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, query, 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'\nimport { readRevocable, showsRevokeButton, showsRevokedNote } from '../utils/credential-panel-state.js'\nimport { changedValues, filledValues, missingRequired } from '../utils/form-values.js'\n\n/**\n * Registering and editing an appliance, in a popup.\n *\n * ── Why this replaced two pages ─────────────────────────────────────────────\n * `appliance-register` and `appliance/{id}` were full routes. Nothing outside auth-ui linked\n * to either, and every other resource in this package — role, user, domain, domain owner,\n * auth provider, attribute set — is a grist list with a popup over it. The two pages were not\n * a different kind of resource, only older code.\n *\n * ── The defect that came with them ──────────────────────────────────────────\n * Both pages sent the whole form. On the register page that meant `serialNo: ''`, and\n * `createAppliance` has no validation at all — it saves `{ domain, creator, updater,\n * ...appliance }` — so `''` passed GraphQL's non-null check (an empty string is a String) and\n * an appliance with no serial number, brand or model was created **and the screen navigated\n * away as if it had worked.** On the edit page the same habit cleared fields nobody touched.\n *\n * `NewAppliance` is the source of the required list below. `AppliancePatch` is spread over the\n * loaded row, so the update sends a diff — see `utils/form-values.ts` for why those are two\n * different rules rather than one.\n */\n\n/** Fields `NewAppliance` declares non-null. Not a house rule — the schema's own list. */\nconst REQUIRED = ['serialNo', 'name', 'brand', 'model']\n\nconst APPLIANCE_FIELDS = `\n id\n name\n description\n serialNo\n brand\n model\n netmask\n accessToken\n credentialRevoked\n`\n\n@customElement('appliance-editor')\nexport class ApplianceEditor 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 * ── Why the in-content buttons need a rule of their own ─────────────────\n * CommonPopupStyles makes controls pill-shaped because a popup is a rounded card and the\n * things inside it follow the card. But it says that through `.buttons`, so only the commit\n * row got it: the actions sitting inside a fieldset — generate a new token, generate a new\n * secret — kept the page radius and read as a different kind of button on the same card.\n * Measured on a live popup: 5px inside the content, 13986px in the row below it.\n */\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n /* in-content actions follow the card too, not just the commit row. see the note on styles. */\n fieldset md-text-button,\n fieldset md-filled-button {\n --md-text-button-container-shape: var(--popup-control-radius);\n --md-filled-button-container-shape: var(--popup-control-radius);\n }\n\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 form {\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\n input:focus {\n outline: none;\n }\n\n input[invalid] {\n border-color: var(--md-sys-color-error);\n }\n\n [field-error] {\n color: var(--md-sys-color-error);\n font-size: var(--popup-font-size-small);\n margin-top: var(--spacing-tiny, 2px);\n }\n\n /* The one reverting action goes left, away from the ones that commit. */\n .buttons .left {\n margin-right: auto;\n }\n\n /* taking a credential away is not the same kind of act as issuing one. */\n md-text-button.danger {\n --md-text-button-label-text-color: var(--md-sys-color-error);\n }\n\n .note.revoked {\n color: var(--md-sys-color-error);\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n /** The appliance to edit. Absent means this popup is registering a new one. */\n @property({ type: String }) applianceId?: string\n\n @query('form') form!: HTMLFormElement\n\n /** The loaded record. The patch is a diff against this, so it is never rebuilt from the form. */\n @state() private appliance: any = null\n\n /** Required fields still empty. Filled in when the person presses save, not while typing. */\n @state() private missing: string[] = []\n\n /** The mutation was refused. The shared link already said why; this says the press landed. */\n @state() private failed: boolean = false\n\n /** The single-record read did not answer. Distinct from answering with nothing. */\n @state() private loadFailed: boolean = false\n\n /*\n * Does revoking work on this installation, for this reader?\n *\n * Revocation is behind a configuration switch, and the server answers one question that folds\n * the switch and the reader's privilege together — see `applianceCredentialRevocable`. False\n * until that answer comes back, so a slow or refused read shows no button rather than one that\n * refuses when pressed.\n */\n @state() private revocable: boolean = false\n\n private get editing(): boolean {\n return !!this.applianceId\n }\n\n render() {\n const appliance = this.appliance || {}\n const exp = appliance.accessToken ? parseJwt(appliance.accessToken)?.exp : undefined\n\n if (this.editing && this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the appliance could not be loaded')}</div>`\n }\n\n return html`\n <form @submit=${(e: Event) => e.preventDefault()}>\n <fieldset>\n <legend>${i18next.t('text.appliance')}</legend>\n <div field>\n <label for=\"name\">${i18next.t('field.name')}</label>\n <input\n id=\"name\"\n type=\"text\"\n name=\"name\"\n .value=${appliance.name || ''}\n required\n autofocus\n ?invalid=${this.missing.includes('name')}\n />\n ${this.fieldError('name')}\n </div>\n\n <div field>\n <label for=\"description\">${i18next.t('field.description')}</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${appliance.description || ''} />\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"serial-no\">${i18next.t('field.serial-no')}</label>\n <input\n id=\"serial-no\"\n type=\"text\"\n name=\"serialNo\"\n .value=${appliance.serialNo || ''}\n required\n ?invalid=${this.missing.includes('serialNo')}\n />\n ${this.fieldError('serialNo')}\n </div>\n\n <div field>\n <label for=\"brand\">${i18next.t('field.brand')}</label>\n <input\n id=\"brand\"\n type=\"text\"\n name=\"brand\"\n .value=${appliance.brand || ''}\n required\n ?invalid=${this.missing.includes('brand')}\n />\n ${this.fieldError('brand')}\n </div>\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"model\">${i18next.t('field.model')}</label>\n <input\n id=\"model\"\n type=\"text\"\n name=\"model\"\n .value=${appliance.model || ''}\n required\n ?invalid=${this.missing.includes('model')}\n />\n ${this.fieldError('model')}\n </div>\n\n <div field>\n <label for=\"netmask\">${i18next.t('field.netmask')}</label>\n <input id=\"netmask\" type=\"text\" name=\"netmask\" .value=${appliance.netmask || ''} />\n </div>\n </div>\n </fieldset>\n\n ${this.editing ? this.renderCredential(appliance, exp) : ''}\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </form>\n\n <div class=\"buttons\">\n ${\n this.editing\n ? html`\n <md-text-button class=\"left\" @click=${this.deleteAppliance.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n `\n : ''\n }\n <md-text-button @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button @click=${this.save.bind(this)}>\n ${this.editing ? i18next.t('button.update') : i18next.t('button.register')}\n </md-filled-button>\n </div>\n `\n }\n\n /*\n * The credential panel only exists once the appliance does — there is no token to show\n * before it is created, and the list query deliberately never asks for one.\n *\n * ── Why a revoked appliance says so ─────────────────────────────────────\n * Revoking clears the token, so without a word for it the panel looks exactly like one whose\n * credential was never issued. Those are opposite situations: one is waiting to be set up,\n * the other has been deliberately shut off, and only one of them wants somebody to act.\n *\n * ── Why the revoke button is not always here ────────────────────────────\n * It appears only where the installation turned revocation on and the reader may use it. See\n * the note on `revocable`.\n */\n private renderCredential(appliance: any, exp?: number) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.appliance credential')}</legend>\n <div field>\n <label for=\"access-token\">${i18next.t('label.access token')}</label>\n <secret-field id=\"access-token\" .value=${appliance.accessToken || ''}></secret-field>\n ${\n showsRevokedNote(appliance)\n ? html`<div class=\"note revoked\">${i18next.t('text.this credential has been revoked')}</div>`\n : ''\n }\n ${\n exp\n ? html`<div class=\"note\">\n ${i18next.t('text.token expiry time')} ${new Date(exp * 1000).toLocaleString()} :\n ${asyncReplace(this.expTimer(exp))}\n </div>`\n : ''\n }\n <div class=\"row\">\n <md-text-button @click=${this.generateSecret.bind(this)}>\n ${i18next.t('button.generate new access token')}\n </md-text-button>\n ${\n showsRevokeButton(this.revocable, appliance)\n ? html`\n <md-text-button class=\"danger\" @click=${this.revokeCredential.bind(this)}>\n ${i18next.t('button.revoke credential')}\n </md-text-button>\n `\n : ''\n }\n </div>\n </div>\n </fieldset>\n `\n }\n\n private fieldError(name: string) {\n return this.missing.includes(name) ? html`<div field-error>${i18next.t('text.this field is required')}</div>` : ''\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n if (this.editing) {\n await Promise.all([this.fetchAppliance(), this.fetchRevocable()])\n }\n }\n\n /*\n * Whether the revoke button exists at all.\n *\n * A refusal arrives as `response.errors` rather than a throw, and both a refusal and a `false`\n * mean the same thing to this screen: no button. So there is nothing to report here — the\n * absence of the button is the whole answer.\n */\n private async fetchRevocable() {\n const response = await client.query({\n query: gql`\n query {\n applianceCredentialRevocable\n }\n `\n })\n\n this.revocable = readRevocable(response as any)\n }\n\n async fetchAppliance() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n appliance(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n /*\n * `errorPolicy: 'all'` means a refusal arrives here rather than throwing. Reading\n * `data.appliance` without checking would show an empty form that looks like a record\n * with every field blank — and saving it would then write those blanks.\n */\n if (response.errors || !response.data?.appliance) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.appliance = response.data.appliance\n }\n\n private cancel(e: Event) {\n e.preventDefault()\n this.close()\n }\n\n async save(e: Event) {\n e.preventDefault()\n\n const formData = new FormData(this.form)\n\n /*\n * The same four fields are required on both paths. They come from `NewAppliance`, and an\n * update is held to them too: the patch is spread over the row, so `name: ''` would clear\n * a field the product will not let you create without.\n */\n const missing = missingRequired(formData, REQUIRED)\n\n if (missing.length) {\n this.missing = missing\n return\n }\n\n this.missing = []\n\n return this.editing ? this.updateAppliance(formData) : this.createAppliance(formData)\n }\n\n private async createAppliance(formData: FormData) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($appliance: NewAppliance!) {\n createAppliance(appliance: $appliance) {\n id\n }\n }\n `,\n variables: { appliance: filledValues(formData) }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async updateAppliance(formData: FormData) {\n const patch = changedValues(formData, this.appliance)\n\n /*\n * Nothing moved. Sending an empty patch would still stamp `updater` and `updatedAt`,\n * which makes an untouched record look edited to whoever reads the list next.\n */\n if (!Object.keys(patch).length) {\n this.close()\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: AppliancePatch!) {\n updateAppliance(id: $id, patch: $patch) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId, patch }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async generateSecret(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.generate new 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 generateApplianceSecret(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open on purpose. A new token is the one value on this screen the reader has to\n * copy before it is out of reach — closing here would hand it to them and take it away.\n */\n this.failed = false\n this.appliance = response.data.generateApplianceSecret\n this.dispatchEvent(new CustomEvent('appliance-changed', { bubbles: true, composed: true }))\n }\n\n /*\n * Take the credential away and leave the appliance where it is.\n *\n * ── Why this is not the delete button ───────────────────────────────────\n * Deleting takes the appliance and its history with it. This is for a device that is lost,\n * decommissioned or suspect and whose record still has to stand — the row keeps pointing at\n * the same identity, and issuing a new secret later picks that identity back up.\n *\n * ── Why the popup stays open ────────────────────────────────────────────\n * The panel has to show the result: the token is gone and the appliance now says it was\n * revoked. Closing would leave the reader guessing whether the press landed, and the list\n * behind it is refetched either way by the event below.\n */\n private async revokeCredential(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.revoke credential'),\n text: i18next.t('text.revoking stops every token this appliance holds'),\n confirmButton: { text: i18next.t('button.revoke credential') },\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 revokeApplianceCredential(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.failed = false\n this.appliance = response.data.revokeApplianceCredential\n this.dispatchEvent(new CustomEvent('appliance-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteAppliance(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.appliance?.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 deleteAppliance(id: $id)\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n /** The list has to refetch, and the popup is done. */\n private saved() {\n this.failed = false\n this.dispatchEvent(new CustomEvent('appliance-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":"appliance-editor.js","sourceRoot":"","sources":["../../client/components/appliance-editor.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, query, 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'\nimport { readRevocable, showsRevokeButton, showsRevokedNote } from '../utils/credential-panel-state.js'\nimport { changedValues, filledValues, missingRequired } from '../utils/form-values.js'\n\n/**\n * Registering and editing an appliance, in a popup.\n *\n * ── Why this replaced two pages ─────────────────────────────────────────────\n * `appliance-register` and `appliance/{id}` were full routes. Nothing outside auth-ui linked\n * to either, and every other resource in this package — role, user, domain, domain owner,\n * auth provider, attribute set — is a grist list with a popup over it. The two pages were not\n * a different kind of resource, only older code.\n *\n * ── The defect that came with them ──────────────────────────────────────────\n * Both pages sent the whole form. On the register page that meant `serialNo: ''`, and\n * `createAppliance` has no validation at all — it saves `{ domain, creator, updater,\n * ...appliance }` — so `''` passed GraphQL's non-null check (an empty string is a String) and\n * an appliance with no serial number, brand or model was created **and the screen navigated\n * away as if it had worked.** On the edit page the same habit cleared fields nobody touched.\n *\n * `NewAppliance` is the source of the required list below. `AppliancePatch` is spread over the\n * loaded row, so the update sends a diff — see `utils/form-values.ts` for why those are two\n * different rules rather than one.\n */\n\n/** Fields `NewAppliance` declares non-null. Not a house rule — the schema's own list. */\nconst REQUIRED = ['serialNo', 'name', 'brand', 'model']\n\nconst APPLIANCE_FIELDS = `\n id\n name\n description\n serialNo\n brand\n model\n netmask\n accessToken\n credentialRevoked\n`\n\n@customElement('appliance-editor')\nexport class ApplianceEditor 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 * ── Why the in-content buttons need a rule of their own ─────────────────\n * CommonPopupStyles makes controls pill-shaped because a popup is a rounded card and the\n * things inside it follow the card. But it says that through `.buttons`, so only the commit\n * row got it: the actions sitting inside a fieldset — generate a new token, generate a new\n * secret — kept the page radius and read as a different kind of button on the same card.\n * Measured on a live popup: 5px inside the content, 13986px in the row below it.\n */\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n /* in-content actions follow the card too, not just the commit row. see the note on styles. */\n fieldset md-text-button,\n fieldset md-filled-button {\n --md-text-button-container-shape: var(--popup-control-radius);\n --md-filled-button-container-shape: var(--popup-control-radius);\n }\n\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 form {\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\n input:focus {\n outline: none;\n }\n\n input[invalid] {\n border-color: var(--md-sys-color-error);\n }\n\n [field-error] {\n color: var(--md-sys-color-error);\n font-size: var(--popup-font-size-small);\n margin-top: var(--spacing-tiny, 2px);\n }\n\n /* taking a credential away is not the same kind of act as issuing one. */\n md-text-button.danger {\n --md-text-button-label-text-color: var(--md-sys-color-error);\n }\n\n .note.revoked {\n color: var(--md-sys-color-error);\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n /** The appliance to edit. Absent means this popup is registering a new one. */\n @property({ type: String }) applianceId?: string\n\n @query('form') form!: HTMLFormElement\n\n /** The loaded record. The patch is a diff against this, so it is never rebuilt from the form. */\n @state() private appliance: any = null\n\n /** Required fields still empty. Filled in when the person presses save, not while typing. */\n @state() private missing: string[] = []\n\n /** The mutation was refused. The shared link already said why; this says the press landed. */\n @state() private failed: boolean = false\n\n /** The single-record read did not answer. Distinct from answering with nothing. */\n @state() private loadFailed: boolean = false\n\n /*\n * Does revoking work on this installation, for this reader?\n *\n * Revocation is behind a configuration switch, and the server answers one question that folds\n * the switch and the reader's privilege together — see `applianceCredentialRevocable`. False\n * until that answer comes back, so a slow or refused read shows no button rather than one that\n * refuses when pressed.\n */\n @state() private revocable: boolean = false\n\n private get editing(): boolean {\n return !!this.applianceId\n }\n\n render() {\n const appliance = this.appliance || {}\n const exp = appliance.accessToken ? parseJwt(appliance.accessToken)?.exp : undefined\n\n if (this.editing && this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the appliance could not be loaded')}</div>`\n }\n\n return html`\n <form @submit=${(e: Event) => e.preventDefault()}>\n <fieldset>\n <legend>${i18next.t('text.appliance')}</legend>\n <div field>\n <label for=\"name\">${i18next.t('field.name')}</label>\n <input\n id=\"name\"\n type=\"text\"\n name=\"name\"\n .value=${appliance.name || ''}\n required\n autofocus\n ?invalid=${this.missing.includes('name')}\n />\n ${this.fieldError('name')}\n </div>\n\n <div field>\n <label for=\"description\">${i18next.t('field.description')}</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${appliance.description || ''} />\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"serial-no\">${i18next.t('field.serial-no')}</label>\n <input\n id=\"serial-no\"\n type=\"text\"\n name=\"serialNo\"\n .value=${appliance.serialNo || ''}\n required\n ?invalid=${this.missing.includes('serialNo')}\n />\n ${this.fieldError('serialNo')}\n </div>\n\n <div field>\n <label for=\"brand\">${i18next.t('field.brand')}</label>\n <input\n id=\"brand\"\n type=\"text\"\n name=\"brand\"\n .value=${appliance.brand || ''}\n required\n ?invalid=${this.missing.includes('brand')}\n />\n ${this.fieldError('brand')}\n </div>\n </div>\n\n <div class=\"row\">\n <div field>\n <label for=\"model\">${i18next.t('field.model')}</label>\n <input\n id=\"model\"\n type=\"text\"\n name=\"model\"\n .value=${appliance.model || ''}\n required\n ?invalid=${this.missing.includes('model')}\n />\n ${this.fieldError('model')}\n </div>\n\n <div field>\n <label for=\"netmask\">${i18next.t('field.netmask')}</label>\n <input id=\"netmask\" type=\"text\" name=\"netmask\" .value=${appliance.netmask || ''} />\n </div>\n </div>\n </fieldset>\n\n ${this.editing ? this.renderCredential(appliance, exp) : ''}\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </form>\n\n <ox-popup-actions>\n ${\n this.editing\n ? html`\n <md-text-button slot=\"start\" destructive @click=${this.deleteAppliance.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n `\n : ''\n }\n <md-text-button slot=\"cancel\" @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" @click=${this.save.bind(this)}>\n ${this.editing ? i18next.t('button.update') : i18next.t('button.register')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /*\n * The credential panel only exists once the appliance does — there is no token to show\n * before it is created, and the list query deliberately never asks for one.\n *\n * ── Why a revoked appliance says so ─────────────────────────────────────\n * Revoking clears the token, so without a word for it the panel looks exactly like one whose\n * credential was never issued. Those are opposite situations: one is waiting to be set up,\n * the other has been deliberately shut off, and only one of them wants somebody to act.\n *\n * ── Why the revoke button is not always here ────────────────────────────\n * It appears only where the installation turned revocation on and the reader may use it. See\n * the note on `revocable`.\n */\n private renderCredential(appliance: any, exp?: number) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.appliance credential')}</legend>\n <div field>\n <label for=\"access-token\">${i18next.t('label.access token')}</label>\n <secret-field id=\"access-token\" .value=${appliance.accessToken || ''}></secret-field>\n ${\n showsRevokedNote(appliance)\n ? html`<div class=\"note revoked\">${i18next.t('text.this credential has been revoked')}</div>`\n : ''\n }\n ${\n exp\n ? html`<div class=\"note\">\n ${i18next.t('text.token expiry time')} ${new Date(exp * 1000).toLocaleString()} :\n ${asyncReplace(this.expTimer(exp))}\n </div>`\n : ''\n }\n <div class=\"row\">\n <md-text-button @click=${this.generateSecret.bind(this)}>\n ${i18next.t('button.generate new access token')}\n </md-text-button>\n ${\n showsRevokeButton(this.revocable, appliance)\n ? html`\n <md-text-button class=\"danger\" @click=${this.revokeCredential.bind(this)}>\n ${i18next.t('button.revoke credential')}\n </md-text-button>\n `\n : ''\n }\n </div>\n </div>\n </fieldset>\n `\n }\n\n private fieldError(name: string) {\n return this.missing.includes(name) ? html`<div field-error>${i18next.t('text.this field is required')}</div>` : ''\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n if (this.editing) {\n await Promise.all([this.fetchAppliance(), this.fetchRevocable()])\n }\n }\n\n /*\n * Whether the revoke button exists at all.\n *\n * A refusal arrives as `response.errors` rather than a throw, and both a refusal and a `false`\n * mean the same thing to this screen: no button. So there is nothing to report here — the\n * absence of the button is the whole answer.\n */\n private async fetchRevocable() {\n const response = await client.query({\n query: gql`\n query {\n applianceCredentialRevocable\n }\n `\n })\n\n this.revocable = readRevocable(response as any)\n }\n\n async fetchAppliance() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n appliance(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n /*\n * `errorPolicy: 'all'` means a refusal arrives here rather than throwing. Reading\n * `data.appliance` without checking would show an empty form that looks like a record\n * with every field blank — and saving it would then write those blanks.\n */\n if (response.errors || !response.data?.appliance) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.appliance = response.data.appliance\n }\n\n private cancel(e: Event) {\n e.preventDefault()\n this.close()\n }\n\n async save(e: Event) {\n e.preventDefault()\n\n const formData = new FormData(this.form)\n\n /*\n * The same four fields are required on both paths. They come from `NewAppliance`, and an\n * update is held to them too: the patch is spread over the row, so `name: ''` would clear\n * a field the product will not let you create without.\n */\n const missing = missingRequired(formData, REQUIRED)\n\n if (missing.length) {\n this.missing = missing\n return\n }\n\n this.missing = []\n\n return this.editing ? this.updateAppliance(formData) : this.createAppliance(formData)\n }\n\n private async createAppliance(formData: FormData) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($appliance: NewAppliance!) {\n createAppliance(appliance: $appliance) {\n id\n }\n }\n `,\n variables: { appliance: filledValues(formData) }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async updateAppliance(formData: FormData) {\n const patch = changedValues(formData, this.appliance)\n\n /*\n * Nothing moved. Sending an empty patch would still stamp `updater` and `updatedAt`,\n * which makes an untouched record look edited to whoever reads the list next.\n */\n if (!Object.keys(patch).length) {\n this.close()\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: AppliancePatch!) {\n updateAppliance(id: $id, patch: $patch) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId, patch }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async generateSecret(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.generate new 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 generateApplianceSecret(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open on purpose. A new token is the one value on this screen the reader has to\n * copy before it is out of reach — closing here would hand it to them and take it away.\n */\n this.failed = false\n this.appliance = response.data.generateApplianceSecret\n this.dispatchEvent(new CustomEvent('appliance-changed', { bubbles: true, composed: true }))\n }\n\n /*\n * Take the credential away and leave the appliance where it is.\n *\n * ── Why this is not the delete button ───────────────────────────────────\n * Deleting takes the appliance and its history with it. This is for a device that is lost,\n * decommissioned or suspect and whose record still has to stand — the row keeps pointing at\n * the same identity, and issuing a new secret later picks that identity back up.\n *\n * ── Why the popup stays open ────────────────────────────────────────────\n * The panel has to show the result: the token is gone and the appliance now says it was\n * revoked. Closing would leave the reader guessing whether the press landed, and the list\n * behind it is refetched either way by the event below.\n */\n private async revokeCredential(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.revoke credential'),\n text: i18next.t('text.revoking stops every token this appliance holds'),\n confirmButton: { text: i18next.t('button.revoke credential') },\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 revokeApplianceCredential(id: $id) { ${APPLIANCE_FIELDS} }\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.failed = false\n this.appliance = response.data.revokeApplianceCredential\n this.dispatchEvent(new CustomEvent('appliance-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteAppliance(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.appliance?.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 deleteAppliance(id: $id)\n }\n `,\n variables: { id: this.applianceId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n /** The list has to refetch, and the popup is done. */\n private saved() {\n this.failed = false\n this.dispatchEvent(new CustomEvent('appliance-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,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
2
3
|
import '@material/web/icon/icon.js';
|
|
3
4
|
import '@material/web/button/text-button.js';
|
|
4
5
|
import '@material/web/button/filled-button.js';
|
|
@@ -179,10 +180,6 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
|
|
|
179
180
|
margin-top: var(--spacing-tiny, 2px);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
.buttons .left {
|
|
183
|
-
margin-right: auto;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
183
|
@media screen and (max-width: 480px) {
|
|
187
184
|
[field] {
|
|
188
185
|
grid-column: span 2;
|
|
@@ -266,19 +263,19 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
|
|
|
266
263
|
${this.failed ? html `<div class="failure">${i18next.t('text.could not be saved')}</div>` : ''}
|
|
267
264
|
</form>
|
|
268
265
|
|
|
269
|
-
<
|
|
266
|
+
<ox-popup-actions>
|
|
270
267
|
${this.editing
|
|
271
268
|
? html `
|
|
272
|
-
<md-text-button
|
|
269
|
+
<md-text-button slot="start" destructive @click=${this.deleteApplication.bind(this)}>
|
|
273
270
|
${i18next.t('button.delete')}
|
|
274
271
|
</md-text-button>
|
|
275
272
|
`
|
|
276
273
|
: ''}
|
|
277
|
-
<md-text-button @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>
|
|
278
|
-
<md-filled-button @click=${this.save.bind(this)}>
|
|
274
|
+
<md-text-button slot="cancel" @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>
|
|
275
|
+
<md-filled-button slot="confirm" @click=${this.save.bind(this)}>
|
|
279
276
|
${this.editing ? i18next.t('button.update') : i18next.t('button.register')}
|
|
280
277
|
</md-filled-button>
|
|
281
|
-
</
|
|
278
|
+
</ox-popup-actions>
|
|
282
279
|
`;
|
|
283
280
|
}
|
|
284
281
|
/*
|