@things-factory/auth-ui 10.1.16 → 10.1.18

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.
Files changed (98) hide show
  1. package/dist-client/components/app-binding-detail.d.ts +46 -0
  2. package/dist-client/components/app-binding-detail.js +384 -0
  3. package/dist-client/components/app-binding-detail.js.map +1 -0
  4. package/dist-client/components/appliance-editor.d.ts +37 -0
  5. package/dist-client/components/appliance-editor.js +489 -0
  6. package/dist-client/components/appliance-editor.js.map +1 -0
  7. package/dist-client/components/application-editor.d.ts +31 -0
  8. package/dist-client/components/application-editor.js +491 -0
  9. package/dist-client/components/application-editor.js.map +1 -0
  10. package/dist-client/components/profile-component.js +28 -11
  11. package/dist-client/components/profile-component.js.map +1 -1
  12. package/dist-client/components/secret-field.d.ts +35 -0
  13. package/dist-client/components/secret-field.js +149 -0
  14. package/dist-client/components/secret-field.js.map +1 -0
  15. package/dist-client/constants/application-type-parity.test.js +62 -0
  16. package/dist-client/constants/application-type-parity.test.js.map +1 -0
  17. package/dist-client/constants/application.d.ts +17 -7
  18. package/dist-client/constants/application.js +18 -8
  19. package/dist-client/constants/application.js.map +1 -1
  20. package/dist-client/pages/app-binding/app-binding-list-query.test.js +46 -0
  21. package/dist-client/pages/app-binding/app-binding-list-query.test.js.map +1 -0
  22. package/dist-client/pages/app-binding/app-bindings.d.ts +65 -2
  23. package/dist-client/pages/app-binding/app-bindings.js +217 -117
  24. package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
  25. package/dist-client/pages/appliance/appliance-list-query.test.d.ts +1 -0
  26. package/dist-client/pages/appliance/appliance-list-query.test.js +58 -0
  27. package/dist-client/pages/appliance/appliance-list-query.test.js.map +1 -0
  28. package/dist-client/pages/appliance/home.d.ts +63 -2
  29. package/dist-client/pages/appliance/home.js +219 -123
  30. package/dist-client/pages/appliance/home.js.map +1 -1
  31. package/dist-client/pages/application/applications.d.ts +63 -2
  32. package/dist-client/pages/application/applications.js +212 -129
  33. package/dist-client/pages/application/applications.js.map +1 -1
  34. package/dist-client/pages/auth-provider/auth-provider-management.d.ts +4 -2
  35. package/dist-client/pages/auth-provider/auth-provider-management.js +35 -6
  36. package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
  37. package/dist-client/pages/domain-owner/domain-owner-management.d.ts +2 -0
  38. package/dist-client/pages/domain-owner/domain-owner-management.js +34 -7
  39. package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
  40. package/dist-client/pages/secrets-are-not-printed.test.d.ts +1 -0
  41. package/dist-client/pages/secrets-are-not-printed.test.js +88 -0
  42. package/dist-client/pages/secrets-are-not-printed.test.js.map +1 -0
  43. package/dist-client/route.js +0 -15
  44. package/dist-client/route.js.map +1 -1
  45. package/dist-client/tsconfig.tsbuildinfo +1 -1
  46. package/dist-client/utils/application-status.d.ts +32 -0
  47. package/dist-client/utils/application-status.js +45 -0
  48. package/dist-client/utils/application-status.js.map +1 -0
  49. package/dist-client/utils/application-status.test.d.ts +1 -0
  50. package/dist-client/utils/application-status.test.js +45 -0
  51. package/dist-client/utils/application-status.test.js.map +1 -0
  52. package/dist-client/utils/form-values.d.ts +71 -0
  53. package/dist-client/utils/form-values.js +102 -0
  54. package/dist-client/utils/form-values.js.map +1 -0
  55. package/dist-client/utils/form-values.test.d.ts +1 -0
  56. package/dist-client/utils/form-values.test.js +120 -0
  57. package/dist-client/utils/form-values.test.js.map +1 -0
  58. package/dist-client/utils/list-result.d.ts +62 -0
  59. package/dist-client/utils/list-result.js +92 -0
  60. package/dist-client/utils/list-result.js.map +1 -0
  61. package/dist-client/utils/list-result.test.d.ts +1 -0
  62. package/dist-client/utils/list-result.test.js +160 -0
  63. package/dist-client/utils/list-result.test.js.map +1 -0
  64. package/dist-client/utils/secret-display.d.ts +51 -0
  65. package/dist-client/utils/secret-display.js +61 -0
  66. package/dist-client/utils/secret-display.js.map +1 -0
  67. package/dist-client/utils/secret-display.test.d.ts +1 -0
  68. package/dist-client/utils/secret-display.test.js +59 -0
  69. package/dist-client/utils/secret-display.test.js.map +1 -0
  70. package/dist-client/utils/webauthn-verification.d.ts +56 -0
  71. package/dist-client/utils/webauthn-verification.js +79 -0
  72. package/dist-client/utils/webauthn-verification.js.map +1 -0
  73. package/dist-client/utils/webauthn-verification.test.d.ts +1 -0
  74. package/dist-client/utils/webauthn-verification.test.js +79 -0
  75. package/dist-client/utils/webauthn-verification.test.js.map +1 -0
  76. package/dist-server/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +3 -3
  78. package/things-factory.config.js +0 -5
  79. package/translations/en.json +56 -0
  80. package/translations/ja.json +57 -0
  81. package/translations/ko.json +56 -0
  82. package/translations/ms.json +57 -0
  83. package/translations/zh.json +57 -0
  84. package/dist-client/pages/app-binding/app-binding.d.ts +0 -1
  85. package/dist-client/pages/app-binding/app-binding.js +0 -371
  86. package/dist-client/pages/app-binding/app-binding.js.map +0 -1
  87. package/dist-client/pages/appliance/appliance.d.ts +0 -1
  88. package/dist-client/pages/appliance/appliance.js +0 -392
  89. package/dist-client/pages/appliance/appliance.js.map +0 -1
  90. package/dist-client/pages/appliance/register.js +0 -166
  91. package/dist-client/pages/appliance/register.js.map +0 -1
  92. package/dist-client/pages/application/application.d.ts +0 -1
  93. package/dist-client/pages/application/application.js +0 -377
  94. package/dist-client/pages/application/application.js.map +0 -1
  95. package/dist-client/pages/application/register.js +0 -192
  96. package/dist-client/pages/application/register.js.map +0 -1
  97. /package/dist-client/{pages/appliance/register.d.ts → constants/application-type-parity.test.d.ts} +0 -0
  98. /package/dist-client/pages/{application/register.d.ts → app-binding/app-binding-list-query.test.d.ts} +0 -0
@@ -1,154 +1,250 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
- import '@material/web/button/elevated-button.js';
3
- import '@material/web/button/outlined-button.js';
2
+ import '@material/web/icon/icon.js';
3
+ import '@operato/data-grist/ox-grist.js';
4
+ import '@operato/context/ox-context-page-toolbar.js';
4
5
  import gql from 'graphql-tag';
5
6
  import { css, html } from 'lit';
6
- import { customElement, property } from 'lit/decorators.js';
7
- import { client } from '@operato/graphql';
8
- import { navigate, PageView } from '@operato/shell';
9
- let Appliances = class Appliances extends PageView {
7
+ import { customElement, property, query } from 'lit/decorators.js';
8
+ import { client, gqlContext } from '@operato/graphql';
9
+ import { i18next, localize } from '@operato/i18n';
10
+ import { openPopup } from '@operato/layout';
11
+ import { PageView } from '@operato/shell';
12
+ import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles';
13
+ import { isMobileDevice } from '@operato/utils';
14
+ import { p13n } from '@operato/p13n';
15
+ import '../../components/appliance-editor.js';
16
+ import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
17
+ /**
18
+ * Registered appliances.
19
+ *
20
+ * ── What this screen looked like before ──────────────────────────────────────
21
+ * A hand-written table with no paging, sorting or search, and three columns that were not
22
+ * data: "API health" printed OK, "Installs" printed 1, "status" printed draft, identical on
23
+ * every row. An appliance that had never checked in read exactly like one reporting fine.
24
+ * Those are gone — Appliance declares none of the three — and the columns here are the
25
+ * fields it does declare.
26
+ *
27
+ * ── ⚠ The access token is not asked for, and a test holds that ──────────────
28
+ * `Appliance.accessToken` carries its own
29
+ * `@privilege(category: "security", privilege: "query", domainOwnerGranted: true)`. A list
30
+ * is the wrong place for it regardless of who may read it: a token in a grid cell is a token
31
+ * in plain text beside a name, visible to everyone who can open the screen and to anyone
32
+ * looking over their shoulder. The screens that genuinely need it ask one record at a time.
33
+ *
34
+ * `appliance-list-query.test.ts` fails if the word reappears here.
35
+ */
36
+ let Appliances = class Appliances extends p13n(localize(i18next)(PageView)) {
10
37
  constructor() {
11
38
  super(...arguments);
12
- this.appliances = [];
39
+ this.active = false;
40
+ this.mode = isMobileDevice() ? 'CARD' : 'GRID';
41
+ /** The last fetch did not answer. Distinct from answering with nothing. */
42
+ this.failed = false;
13
43
  }
14
44
  static { this.styles = [
45
+ CommonGristStyles,
46
+ CommonHeaderStyles,
47
+ ScrollbarStyles,
15
48
  css `
16
- :host { background-color: var(--md-sys-color-background);
17
- padding: var(--spacing-large);
18
-
19
- overflow: auto;
20
- }
21
-
22
- md-elevated-button { text-transform: capitalize;
23
- }
24
-
25
- md-outlined-button { float: right;
26
- margin-top: var(--spacing-medium);
27
- text-transform: capitalize;
28
- }
29
-
30
- h2 { margin: var(--title-margin);
31
- font: var(--title-font);
32
- color: var(--title-text-color);
33
- }
34
-
35
- [page-description] { margin: var(--page-description-margin);
36
- font: var(--page-description-font);
37
- color: var(--page-description-color);
38
- }
39
-
40
- table { width: 100%;
41
- margin: var(--spacing-large) 0;
42
- border-collapse: collapse;
43
- }
44
-
45
- tr { background-color: var(--tr-background-color);
46
- }
47
-
48
- tr:nth-child(odd) { background-color: var(--tr-background-odd-color);
49
- }
50
-
51
- tr:hover { background-color: var(--tr-background-hover-color);
52
- }
53
-
54
- th { border-top: var(--th-border-top);
55
- border-bottom: var(--td-border-bottom);
56
- padding: var(--th-padding);
57
-
58
- font: var(--th-font);
59
- color: var(--th-color);
60
- text-transform: var(--th-text-transform);
61
- text-align: left;
62
- }
63
-
64
- td { padding: var(--td-padding);
65
- border-bottom: var(--td-border-bottom);
66
- font: var(--td-font);
67
- color: var(--td-color);
68
- }
69
-
70
- td a { color: var(--md-sys-color-primary);
71
- font: bold 16px var(--theme-font);
72
-
73
- display: block;
74
- text-decoration: none;
75
- }
76
-
77
- .text-align-center { text-align: center;
78
- }
79
-
80
- .text-align-right { text-align: right;
81
- }
82
-
83
- @media screen and (max-width: 480px) { :host { padding: var(--spacing-medium);
84
- }
85
- }
49
+ :host {
50
+ display: flex;
51
+ flex-direction: column;
52
+ overflow: hidden;
53
+ }
54
+
55
+ ox-grist {
56
+ overflow-y: auto;
57
+ flex: 1;
58
+ }
59
+
60
+ [failed] {
61
+ padding: var(--spacing-medium);
62
+ color: var(--md-sys-color-error);
63
+ font: var(--label-font, inherit);
64
+ }
86
65
  `
87
66
  ]; }
88
67
  get context() {
89
- return { title: `appliance home`,
90
- help: 'auth/appliance'
68
+ return {
69
+ title: i18next.t('text.appliances'),
70
+ help: 'auth/appliance',
71
+ search: {
72
+ handler: (search) => {
73
+ this.grist.searchText = search;
74
+ },
75
+ value: this.grist?.searchText || ''
76
+ },
77
+ actions: [
78
+ {
79
+ title: i18next.t('button.register'),
80
+ action: () => this.openEditor(),
81
+ icon: 'add'
82
+ }
83
+ ],
84
+ toolbar: false
91
85
  };
92
86
  }
93
87
  render() {
94
- var appliances = this.appliances || [];
95
88
  return html `
96
- <div>
97
- <h2>Registered Appliances</h2>
98
- <p page-description>
99
- What type of appliance are you building?<br />Choose the app type that best suits the audience you’re building
100
- for. The appliance type can’t be changed after it’s created.
101
- </p>
102
- <md-elevated-button @click=${e => navigate('appliance-register')}>register new appliance</md-elevated-button>
103
- </div>
104
-
105
- <table>
106
- <tr>
107
- <th>appliance name</th>
108
- <th>API health</th>
109
- <th>Installs</th>
110
- <th>status</th>
111
- </tr>
112
- ${appliances.map(appliance => html `
113
- <tr>
114
- <td>
115
- <a href=${`appliance/${appliance.id}`}>${appliance.name}</a>
116
- ${appliance.description}
117
- </td>
118
- <td>OK</td>
119
- <td class="text-align-center">1</td>
120
- <td>draft</td>
121
- </tr>
122
- `)}
123
- </table>
89
+ <ox-grist
90
+ .mode=${this.mode}
91
+ .config=${this.config}
92
+ .fetchHandler=${this.fetchHandler.bind(this)}
93
+ .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
94
+ >
95
+ <div slot="headroom" class="header">
96
+ <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
97
+ </div>
98
+ </ox-grist>
99
+
100
+ ${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
124
101
  `;
125
102
  }
126
- async pageUpdated() {
103
+ async pageInitialized() {
104
+ this.config = {
105
+ rows: { appendable: false, selectable: { multiple: false } },
106
+ columns: [
107
+ { type: 'gutter', gutterName: 'sequence' },
108
+ {
109
+ type: 'gutter',
110
+ gutterName: 'button',
111
+ icon: 'chevron_right',
112
+ title: i18next.t('button.detail'),
113
+ handlers: {
114
+ click: (_columns, _data, _column, record) => {
115
+ if (record?.id) {
116
+ this.openEditor(record.id);
117
+ }
118
+ }
119
+ }
120
+ },
121
+ {
122
+ type: 'string',
123
+ name: 'name',
124
+ header: i18next.t('field.name'),
125
+ record: { editable: false },
126
+ sortable: true,
127
+ width: 180
128
+ },
129
+ {
130
+ type: 'string',
131
+ name: 'serialNo',
132
+ header: i18next.t('field.serial-no'),
133
+ record: { editable: false },
134
+ sortable: true,
135
+ width: 160
136
+ },
137
+ {
138
+ type: 'string',
139
+ name: 'brand',
140
+ header: i18next.t('field.brand'),
141
+ record: { editable: false },
142
+ sortable: true,
143
+ width: 140
144
+ },
145
+ {
146
+ type: 'string',
147
+ name: 'model',
148
+ header: i18next.t('field.model'),
149
+ record: { editable: false },
150
+ sortable: true,
151
+ width: 140
152
+ },
153
+ {
154
+ type: 'string',
155
+ name: 'netmask',
156
+ header: i18next.t('field.netmask'),
157
+ record: { editable: false },
158
+ width: 160
159
+ },
160
+ {
161
+ type: 'string',
162
+ name: 'description',
163
+ header: i18next.t('field.description'),
164
+ record: { editable: false },
165
+ width: 240
166
+ },
167
+ {
168
+ type: 'datetime',
169
+ name: 'updatedAt',
170
+ header: i18next.t('field.updated at'),
171
+ sortable: true,
172
+ width: 180
173
+ }
174
+ ]
175
+ };
176
+ }
177
+ /*
178
+ * Registering and editing both happen here, over the list, the way every other resource in
179
+ * this package works. `appliance-register` and `appliance/{id}` were routes; nothing outside
180
+ * auth-ui linked to either.
181
+ */
182
+ openEditor(applianceId) {
183
+ const popup = openPopup(html `<appliance-editor
184
+ .applianceId=${applianceId}
185
+ @appliance-changed=${() => this.grist.fetch()}
186
+ @close=${() => popup.close()}
187
+ ></appliance-editor>`, {
188
+ backdrop: true,
189
+ size: 'medium',
190
+ title: applianceId ? i18next.t('text.appliance') : i18next.t('text.register new appliance')
191
+ });
192
+ }
193
+ async pageUpdated(_changes, _lifecycle) {
127
194
  if (this.active) {
128
- this.appliances = (await this.fetchAppliances()).items;
195
+ await this.updateComplete;
196
+ this.grist.fetch();
129
197
  }
130
198
  }
131
- async fetchAppliances() {
132
- const response = await client.query({ query: gql `
133
- query { appliances { items { id
199
+ async fetchHandler({ page, limit, sortings = [], filters = [] }) {
200
+ const response = await client.query({
201
+ query: gql `
202
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
203
+ appliances(filters: $filters, pagination: $pagination, sortings: $sortings) {
204
+ items {
205
+ id
134
206
  name
207
+ serialNo
208
+ brand
209
+ model
210
+ netmask
135
211
  description
136
- }
212
+ updatedAt
213
+ }
137
214
  total
138
- }
139
- }
140
- `
141
- });
142
- if (!response.errors) {
143
- return response.data.appliances;
215
+ }
144
216
  }
217
+ `,
218
+ variables: { filters, pagination: { page, limit }, sortings },
219
+ context: gqlContext()
220
+ });
221
+ const { items, total, failed } = listOutcome(pageFromResponse(response, 'appliances'));
222
+ this.failed = failed;
223
+ return { total, records: items };
145
224
  }
146
225
  };
147
226
  __decorate([
148
- property({ type: Array }),
149
- __metadata("design:type", Array)
150
- ], Appliances.prototype, "appliances", void 0);
227
+ property({ type: Boolean }),
228
+ __metadata("design:type", Boolean)
229
+ ], Appliances.prototype, "active", void 0);
230
+ __decorate([
231
+ property({ type: Object }),
232
+ __metadata("design:type", Object)
233
+ ], Appliances.prototype, "config", void 0);
234
+ __decorate([
235
+ property({ type: String }),
236
+ __metadata("design:type", String)
237
+ ], Appliances.prototype, "mode", void 0);
238
+ __decorate([
239
+ property({ type: Boolean }),
240
+ __metadata("design:type", Boolean)
241
+ ], Appliances.prototype, "failed", void 0);
242
+ __decorate([
243
+ query('ox-grist'),
244
+ __metadata("design:type", Function)
245
+ ], Appliances.prototype, "grist", void 0);
151
246
  Appliances = __decorate([
152
247
  customElement('appliance-home')
153
248
  ], Appliances);
249
+ export { Appliances };
154
250
  //# sourceMappingURL=home.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"home.js","sourceRoot":"","sources":["../../../client/pages/appliance/home.ts"],"names":[],"mappings":";AAAA,OAAO,yCAAyC,CAAA;AAChD,OAAO,yCAAyC,CAAA;AAEhD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGnD,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,QAAQ;IAAjC;;QA2E6B,eAAU,GAAU,EAAE,CAAA;IA6DnD,CAAC;aAxI4C,WAAM,GAAG;QAClD,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuEF;KACF,AAzEgD,CAyEhD;IAID,IAAI,OAAO;QAAS,OAAO,EAAQ,KAAK,EAAE,gBAAgB;YACtD,IAAI,EAAE,gBAAgB;SAC1B,CAAA;IACD,CAAC;IAEA,MAAM;QAAS,IAAI,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAA;QAEnD,OAAO,IAAI,CAAA;;;;;;;qCAOsB,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;;;;;;;;;;UAU9D,UAAU,CAAC,GAAG,CACd,SAAS,CAAC,EAAE,CAAC,IAAI,CAAA;;;0BAGD,aAAa,SAAS,CAAC,EAAE,EAAE,IAAI,SAAS,CAAC,IAAI;kBACrD,SAAS,CAAC,WAAW;;;;;;WAM5B,CACF;;KAEJ,CAAA;IACJ,CAAC;IAEA,KAAK,CAAC,WAAW;QAAS,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAAO,IAAI,CAAC,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,KAAK,CAAA;QAC1G,CAAC;IACD,CAAC;IAEA,KAAK,CAAC,eAAe;QAAS,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAQ,KAAK,EAAE,GAAG,CAAA;;;;;;;;OAQ/E;SACL,CAAC,CAAA;QAEC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAAO,OAAO,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAA;QAChE,CAAC;IACD,CAAC;;AA5D2B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;8CAAuB;AA3E7C,UAAU;IADf,aAAa,CAAC,gBAAgB,CAAC;GAC1B,UAAU,CAwIf","sourcesContent":["import '@material/web/button/elevated-button.js'\nimport '@material/web/button/outlined-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { navigate, PageView } from '@operato/shell'\n\n@customElement('appliance-home')\nclass Appliances extends PageView { static styles = [\n css`\n :host { background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\n\n overflow: auto;\n }\n\n md-elevated-button { text-transform: capitalize;\n }\n\n md-outlined-button { float: right;\n margin-top: var(--spacing-medium);\n text-transform: capitalize;\n }\n\n h2 { margin: var(--title-margin);\n font: var(--title-font);\n color: var(--title-text-color);\n }\n\n [page-description] { margin: var(--page-description-margin);\n font: var(--page-description-font);\n color: var(--page-description-color);\n }\n\n table { width: 100%;\n margin: var(--spacing-large) 0;\n border-collapse: collapse;\n }\n\n tr { background-color: var(--tr-background-color);\n }\n\n tr:nth-child(odd) { background-color: var(--tr-background-odd-color);\n }\n\n tr:hover { background-color: var(--tr-background-hover-color);\n }\n\n th { border-top: var(--th-border-top);\n border-bottom: var(--td-border-bottom);\n padding: var(--th-padding);\n\n font: var(--th-font);\n color: var(--th-color);\n text-transform: var(--th-text-transform);\n text-align: left;\n }\n\n td { padding: var(--td-padding);\n border-bottom: var(--td-border-bottom);\n font: var(--td-font);\n color: var(--td-color);\n }\n\n td a { color: var(--md-sys-color-primary);\n font: bold 16px var(--theme-font);\n\n display: block;\n text-decoration: none;\n }\n\n .text-align-center { text-align: center;\n }\n\n .text-align-right { text-align: right;\n }\n\n @media screen and (max-width: 480px) { :host { padding: var(--spacing-medium);\n }\n }\n `\n ]\n\n @property({ type: Array }) appliances: any[] = []\n\n get context() { return { title: `appliance home`,\n help: 'auth/appliance'\n }\n }\n\n render() { var appliances = this.appliances || []\n\n return html`\n <div>\n <h2>Registered Appliances</h2>\n <p page-description>\n What type of appliance are you building?<br />Choose the app type that best suits the audience you’re building\n for. The appliance type can’t be changed after it’s created.\n </p>\n <md-elevated-button @click=${e => navigate('appliance-register')}>register new appliance</md-elevated-button>\n </div>\n\n <table>\n <tr>\n <th>appliance name</th>\n <th>API health</th>\n <th>Installs</th>\n <th>status</th>\n </tr>\n ${appliances.map(\n appliance => html`\n <tr>\n <td>\n <a href=${`appliance/${appliance.id}`}>${appliance.name}</a>\n ${appliance.description}\n </td>\n <td>OK</td>\n <td class=\"text-align-center\">1</td>\n <td>draft</td>\n </tr>\n `\n )}\n </table>\n `\n }\n\n async pageUpdated() { if (this.active) { this.appliances = (await this.fetchAppliances()).items\n }\n }\n\n async fetchAppliances() { const response = await client.query({ query: gql`\n query { appliances { items { id\n name\n description\n }\n total\n }\n }\n `\n })\n\n if (!response.errors) { return response.data.appliances\n }\n }\n}\n"]}
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../../../client/pages/appliance/home.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\nimport '@operato/context/ox-context-page-toolbar.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/appliance-editor.js'\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Registered appliances.\n *\n * ── What this screen looked like before ──────────────────────────────────────\n * A hand-written table with no paging, sorting or search, and three columns that were not\n * data: \"API health\" printed OK, \"Installs\" printed 1, \"status\" printed draft, identical on\n * every row. An appliance that had never checked in read exactly like one reporting fine.\n * Those are gone — Appliance declares none of the three — and the columns here are the\n * fields it does declare.\n *\n * ── ⚠ The access token is not asked for, and a test holds that ──────────────\n * `Appliance.accessToken` carries its own\n * `@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)`. A list\n * is the wrong place for it regardless of who may read it: a token in a grid cell is a token\n * in plain text beside a name, visible to everyone who can open the screen and to anyone\n * looking over their shoulder. The screens that genuinely need it ask one record at a time.\n *\n * `appliance-list-query.test.ts` fails if the word reappears here.\n */\n@customElement('appliance-home')\nexport class Appliances extends p13n(localize(i18next)(PageView)) {\n static styles = [\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.appliances'),\n help: 'auth/appliance',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.register'),\n action: () => this.openEditor(),\n icon: 'add'\n }\n ],\n toolbar: false\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <ox-context-page-toolbar class=\"actions\" .context=${this.context}></ox-context-page-toolbar>\n </div>\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openEditor(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n type: 'string',\n name: 'serialNo',\n header: i18next.t('field.serial-no'),\n record: { editable: false },\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'brand',\n header: i18next.t('field.brand'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'model',\n header: i18next.t('field.model'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'netmask',\n header: i18next.t('field.netmask'),\n record: { editable: false },\n width: 160\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n width: 240\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated at'),\n sortable: true,\n width: 180\n }\n ]\n }\n }\n\n /*\n * Registering and editing both happen here, over the list, the way every other resource in\n * this package works. `appliance-register` and `appliance/{id}` were routes; nothing outside\n * auth-ui linked to either.\n */\n private openEditor(applianceId?: string) {\n const popup = openPopup(\n html`<appliance-editor\n .applianceId=${applianceId}\n @appliance-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></appliance-editor>`,\n {\n backdrop: true,\n size: 'medium',\n title: applianceId ? i18next.t('text.appliance') : i18next.t('text.register new appliance')\n }\n )\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n appliances(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n name\n serialNo\n brand\n model\n netmask\n description\n updatedAt\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n context: gqlContext()\n })\n\n const { items, total, failed } = listOutcome(pageFromResponse(response, 'appliances'))\n\n this.failed = failed\n\n return { total, records: items }\n }\n}\n"]}
@@ -1,2 +1,63 @@
1
- import '@material/web/button/elevated-button.js';
2
- import '@material/web/button/outlined-button.js';
1
+ import '@material/web/icon/icon.js';
2
+ import '@operato/data-grist/ox-grist.js';
3
+ import '@operato/context/ox-context-page-toolbar.js';
4
+ import type { FetchOption } from '@operato/data-grist';
5
+ import { PageView } from '@operato/shell';
6
+ import '../../components/application-editor.js';
7
+ declare const Applications_base: (new (...args: any[]) => {
8
+ __preferenceProviders: {
9
+ [element: string]: import("@operato/p13n").PagePreferenceProvider;
10
+ };
11
+ getPagePreferenceProvider(element: string): import("@operato/p13n").PagePreferenceProvider | undefined;
12
+ }) & (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
13
+ /**
14
+ * Registered applications.
15
+ *
16
+ * ── What this screen looked like before ──────────────────────────────────────
17
+ * A hand-written `<table>` with no paging, no sort and no search, and three columns that
18
+ * were not data at all: "API health" printed OK, "Installs" printed 1, "status" printed
19
+ * draft — written into the markup, identical on every row. The previous commit removed the
20
+ * two the server cannot answer and made status real. This one gives the list the same shape
21
+ * as every other list in the product.
22
+ *
23
+ * The server was already ready for it: `applications` takes ListParam and declares
24
+ * `searchables: ['name', 'description']`, so paging, sorting and the header's search box all
25
+ * work by passing the grist's own options through.
26
+ *
27
+ * ── Why the row does not carry health or install counts ──────────────────────
28
+ * Because Application does not declare them. If they arrive one day, they are columns; until
29
+ * then the screen says what it knows. See `utils/application-status.ts` for the matching rule
30
+ * on values the screen has no word for.
31
+ */
32
+ export declare class Applications extends Applications_base {
33
+ static styles: import("lit").CSSResult[];
34
+ active: boolean;
35
+ config: any;
36
+ mode: 'CARD' | 'LIST' | 'GRID';
37
+ /** The last fetch did not answer. Distinct from answering with nothing. */
38
+ failed: boolean;
39
+ private grist;
40
+ get context(): {
41
+ title: string;
42
+ help: string;
43
+ search: {
44
+ handler: (search: string) => void;
45
+ value: string;
46
+ };
47
+ actions: {
48
+ title: string;
49
+ action: () => void;
50
+ icon: string;
51
+ }[];
52
+ toolbar: boolean;
53
+ };
54
+ render(): import("lit-html").TemplateResult<1>;
55
+ pageInitialized(): Promise<void>;
56
+ private openEditor;
57
+ pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
58
+ fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
59
+ total: number;
60
+ records: any[];
61
+ }>;
62
+ }
63
+ export {};