@things-factory/auth-ui 10.1.13 → 10.1.17
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/role-template-picker.js +6 -8
- package/dist-client/components/role-template-picker.js.map +1 -1
- package/dist-client/components/secret-field.d.ts +35 -0
- package/dist-client/components/secret-field.js +149 -0
- package/dist-client/components/secret-field.js.map +1 -0
- package/dist-client/pages/app-binding/app-binding-list-query.test.d.ts +1 -0
- package/dist-client/pages/app-binding/app-binding-list-query.test.js +46 -0
- package/dist-client/pages/app-binding/app-binding-list-query.test.js.map +1 -0
- package/dist-client/pages/app-binding/app-binding.d.ts +1 -0
- package/dist-client/pages/app-binding/app-binding.js +8 -8
- package/dist-client/pages/app-binding/app-binding.js.map +1 -1
- package/dist-client/pages/app-binding/app-bindings.d.ts +63 -2
- package/dist-client/pages/app-binding/app-bindings.js +199 -117
- package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
- package/dist-client/pages/appliance/appliance-list-query.test.d.ts +1 -0
- package/dist-client/pages/appliance/appliance-list-query.test.js +58 -0
- package/dist-client/pages/appliance/appliance-list-query.test.js.map +1 -0
- package/dist-client/pages/appliance/appliance.d.ts +1 -0
- package/dist-client/pages/appliance/appliance.js +2 -4
- package/dist-client/pages/appliance/appliance.js.map +1 -1
- package/dist-client/pages/appliance/home.d.ts +61 -2
- package/dist-client/pages/appliance/home.js +200 -122
- package/dist-client/pages/appliance/home.js.map +1 -1
- package/dist-client/pages/application/application.d.ts +1 -0
- package/dist-client/pages/application/application.js +10 -3
- package/dist-client/pages/application/application.js.map +1 -1
- package/dist-client/pages/application/applications.d.ts +61 -2
- package/dist-client/pages/application/applications.js +193 -128
- package/dist-client/pages/application/applications.js.map +1 -1
- package/dist-client/pages/application/register.js +81 -27
- package/dist-client/pages/application/register.js.map +1 -1
- package/dist-client/pages/auth-provider/auth-provider-management.d.ts +4 -2
- package/dist-client/pages/auth-provider/auth-provider-management.js +35 -6
- package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
- package/dist-client/pages/domain-owner/domain-owner-management.d.ts +2 -0
- package/dist-client/pages/domain-owner/domain-owner-management.js +34 -7
- package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
- package/dist-client/pages/role/role-list-page.d.ts +20 -0
- package/dist-client/pages/role/role-list-page.js +58 -1
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/pages/role/role-privilege-page.js +1 -10
- package/dist-client/pages/role/role-privilege-page.js.map +1 -1
- package/dist-client/pages/secrets-are-not-printed.test.d.ts +1 -0
- package/dist-client/pages/secrets-are-not-printed.test.js +70 -0
- package/dist-client/pages/secrets-are-not-printed.test.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/application-status.d.ts +32 -0
- package/dist-client/utils/application-status.js +45 -0
- package/dist-client/utils/application-status.js.map +1 -0
- package/dist-client/utils/application-status.test.d.ts +1 -0
- package/dist-client/utils/application-status.test.js +45 -0
- package/dist-client/utils/application-status.test.js.map +1 -0
- package/dist-client/utils/form-values.d.ts +42 -0
- package/dist-client/utils/form-values.js +57 -0
- package/dist-client/utils/form-values.js.map +1 -0
- package/dist-client/utils/form-values.test.d.ts +1 -0
- package/dist-client/utils/form-values.test.js +62 -0
- package/dist-client/utils/form-values.test.js.map +1 -0
- package/dist-client/utils/list-result.d.ts +62 -0
- package/dist-client/utils/list-result.js +92 -0
- package/dist-client/utils/list-result.js.map +1 -0
- package/dist-client/utils/list-result.test.d.ts +1 -0
- package/dist-client/utils/list-result.test.js +160 -0
- package/dist-client/utils/list-result.test.js.map +1 -0
- package/dist-client/utils/privilege-axis.d.ts +52 -0
- package/dist-client/utils/privilege-axis.js +55 -0
- package/dist-client/utils/privilege-axis.js.map +1 -0
- package/dist-client/utils/privilege-axis.test.d.ts +1 -0
- package/dist-client/utils/privilege-axis.test.js +39 -0
- package/dist-client/utils/privilege-axis.test.js.map +1 -0
- package/dist-client/utils/secret-display.d.ts +51 -0
- package/dist-client/utils/secret-display.js +61 -0
- package/dist-client/utils/secret-display.js.map +1 -0
- package/dist-client/utils/secret-display.test.d.ts +1 -0
- package/dist-client/utils/secret-display.test.js +59 -0
- package/dist-client/utils/secret-display.test.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/translations/en.json +33 -0
- package/translations/ja.json +33 -0
- package/translations/ko.json +33 -0
- package/translations/ms.json +33 -0
- package/translations/zh.json +33 -0
|
@@ -1,154 +1,232 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import '@material/web/
|
|
3
|
-
import '@
|
|
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';
|
|
7
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
8
|
+
import { client, gqlContext } from '@operato/graphql';
|
|
9
|
+
import { i18next, localize } from '@operato/i18n';
|
|
8
10
|
import { navigate, PageView } from '@operato/shell';
|
|
9
|
-
|
|
11
|
+
import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles';
|
|
12
|
+
import { isMobileDevice } from '@operato/utils';
|
|
13
|
+
import { p13n } from '@operato/p13n';
|
|
14
|
+
import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
|
|
15
|
+
/**
|
|
16
|
+
* Registered appliances.
|
|
17
|
+
*
|
|
18
|
+
* ── What this screen looked like before ──────────────────────────────────────
|
|
19
|
+
* A hand-written table with no paging, sorting or search, and three columns that were not
|
|
20
|
+
* data: "API health" printed OK, "Installs" printed 1, "status" printed draft, identical on
|
|
21
|
+
* every row. An appliance that had never checked in read exactly like one reporting fine.
|
|
22
|
+
* Those are gone — Appliance declares none of the three — and the columns here are the
|
|
23
|
+
* fields it does declare.
|
|
24
|
+
*
|
|
25
|
+
* ── ⚠ The access token is not asked for, and a test holds that ──────────────
|
|
26
|
+
* `Appliance.accessToken` carries its own
|
|
27
|
+
* `@privilege(category: "security", privilege: "query", domainOwnerGranted: true)`. A list
|
|
28
|
+
* is the wrong place for it regardless of who may read it: a token in a grid cell is a token
|
|
29
|
+
* in plain text beside a name, visible to everyone who can open the screen and to anyone
|
|
30
|
+
* looking over their shoulder. The screens that genuinely need it ask one record at a time.
|
|
31
|
+
*
|
|
32
|
+
* `appliance-list-query.test.ts` fails if the word reappears here.
|
|
33
|
+
*/
|
|
34
|
+
let Appliances = class Appliances extends p13n(localize(i18next)(PageView)) {
|
|
10
35
|
constructor() {
|
|
11
36
|
super(...arguments);
|
|
12
|
-
this.
|
|
37
|
+
this.active = false;
|
|
38
|
+
this.mode = isMobileDevice() ? 'CARD' : 'GRID';
|
|
39
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
40
|
+
this.failed = false;
|
|
13
41
|
}
|
|
14
42
|
static { this.styles = [
|
|
43
|
+
CommonGristStyles,
|
|
44
|
+
CommonHeaderStyles,
|
|
45
|
+
ScrollbarStyles,
|
|
15
46
|
css `
|
|
16
|
-
:host {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
overflow:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
47
|
+
:host {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ox-grist {
|
|
54
|
+
overflow-y: auto;
|
|
55
|
+
flex: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[failed] {
|
|
59
|
+
padding: var(--spacing-medium);
|
|
60
|
+
color: var(--md-sys-color-error);
|
|
61
|
+
font: var(--label-font, inherit);
|
|
62
|
+
}
|
|
86
63
|
`
|
|
87
64
|
]; }
|
|
88
65
|
get context() {
|
|
89
|
-
return {
|
|
90
|
-
|
|
66
|
+
return {
|
|
67
|
+
title: i18next.t('text.appliances'),
|
|
68
|
+
help: 'auth/appliance',
|
|
69
|
+
search: {
|
|
70
|
+
handler: (search) => {
|
|
71
|
+
this.grist.searchText = search;
|
|
72
|
+
},
|
|
73
|
+
value: this.grist?.searchText || ''
|
|
74
|
+
},
|
|
75
|
+
actions: [
|
|
76
|
+
{
|
|
77
|
+
title: i18next.t('button.register'),
|
|
78
|
+
action: () => navigate('appliance-register'),
|
|
79
|
+
icon: 'add'
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
toolbar: false
|
|
91
83
|
};
|
|
92
84
|
}
|
|
93
85
|
render() {
|
|
94
|
-
var appliances = this.appliances || [];
|
|
95
86
|
return html `
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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>
|
|
87
|
+
<ox-grist
|
|
88
|
+
.mode=${this.mode}
|
|
89
|
+
.config=${this.config}
|
|
90
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
91
|
+
.personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
|
|
92
|
+
>
|
|
93
|
+
<div slot="headroom" class="header">
|
|
94
|
+
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
95
|
+
</div>
|
|
96
|
+
</ox-grist>
|
|
97
|
+
|
|
98
|
+
${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
|
|
124
99
|
`;
|
|
125
100
|
}
|
|
126
|
-
async
|
|
101
|
+
async pageInitialized() {
|
|
102
|
+
this.config = {
|
|
103
|
+
rows: { appendable: false, selectable: { multiple: false } },
|
|
104
|
+
columns: [
|
|
105
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
106
|
+
{
|
|
107
|
+
type: 'gutter',
|
|
108
|
+
gutterName: 'button',
|
|
109
|
+
icon: 'chevron_right',
|
|
110
|
+
title: i18next.t('button.detail'),
|
|
111
|
+
handlers: {
|
|
112
|
+
click: (_columns, _data, _column, record) => {
|
|
113
|
+
if (record?.id) {
|
|
114
|
+
navigate(`appliance/${record.id}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'string',
|
|
121
|
+
name: 'name',
|
|
122
|
+
header: i18next.t('field.name'),
|
|
123
|
+
record: { editable: false },
|
|
124
|
+
sortable: true,
|
|
125
|
+
width: 180
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'string',
|
|
129
|
+
name: 'serialNo',
|
|
130
|
+
header: i18next.t('field.serial-no'),
|
|
131
|
+
record: { editable: false },
|
|
132
|
+
sortable: true,
|
|
133
|
+
width: 160
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'string',
|
|
137
|
+
name: 'brand',
|
|
138
|
+
header: i18next.t('field.brand'),
|
|
139
|
+
record: { editable: false },
|
|
140
|
+
sortable: true,
|
|
141
|
+
width: 140
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'string',
|
|
145
|
+
name: 'model',
|
|
146
|
+
header: i18next.t('field.model'),
|
|
147
|
+
record: { editable: false },
|
|
148
|
+
sortable: true,
|
|
149
|
+
width: 140
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: 'string',
|
|
153
|
+
name: 'netmask',
|
|
154
|
+
header: i18next.t('field.netmask'),
|
|
155
|
+
record: { editable: false },
|
|
156
|
+
width: 160
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'string',
|
|
160
|
+
name: 'description',
|
|
161
|
+
header: i18next.t('field.description'),
|
|
162
|
+
record: { editable: false },
|
|
163
|
+
width: 240
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: 'datetime',
|
|
167
|
+
name: 'updatedAt',
|
|
168
|
+
header: i18next.t('field.updated at'),
|
|
169
|
+
sortable: true,
|
|
170
|
+
width: 180
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async pageUpdated(_changes, _lifecycle) {
|
|
127
176
|
if (this.active) {
|
|
128
|
-
|
|
177
|
+
await this.updateComplete;
|
|
178
|
+
this.grist.fetch();
|
|
129
179
|
}
|
|
130
180
|
}
|
|
131
|
-
async
|
|
132
|
-
const response = await client.query({
|
|
133
|
-
|
|
181
|
+
async fetchHandler({ page, limit, sortings = [], filters = [] }) {
|
|
182
|
+
const response = await client.query({
|
|
183
|
+
query: gql `
|
|
184
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
185
|
+
appliances(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
186
|
+
items {
|
|
187
|
+
id
|
|
134
188
|
name
|
|
189
|
+
serialNo
|
|
190
|
+
brand
|
|
191
|
+
model
|
|
192
|
+
netmask
|
|
135
193
|
description
|
|
136
|
-
|
|
194
|
+
updatedAt
|
|
195
|
+
}
|
|
137
196
|
total
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
`
|
|
141
|
-
});
|
|
142
|
-
if (!response.errors) {
|
|
143
|
-
return response.data.appliances;
|
|
197
|
+
}
|
|
144
198
|
}
|
|
199
|
+
`,
|
|
200
|
+
variables: { filters, pagination: { page, limit }, sortings },
|
|
201
|
+
context: gqlContext()
|
|
202
|
+
});
|
|
203
|
+
const { items, total, failed } = listOutcome(pageFromResponse(response, 'appliances'));
|
|
204
|
+
this.failed = failed;
|
|
205
|
+
return { total, records: items };
|
|
145
206
|
}
|
|
146
207
|
};
|
|
147
208
|
__decorate([
|
|
148
|
-
property({ type:
|
|
149
|
-
__metadata("design:type",
|
|
150
|
-
], Appliances.prototype, "
|
|
209
|
+
property({ type: Boolean }),
|
|
210
|
+
__metadata("design:type", Boolean)
|
|
211
|
+
], Appliances.prototype, "active", void 0);
|
|
212
|
+
__decorate([
|
|
213
|
+
property({ type: Object }),
|
|
214
|
+
__metadata("design:type", Object)
|
|
215
|
+
], Appliances.prototype, "config", void 0);
|
|
216
|
+
__decorate([
|
|
217
|
+
property({ type: String }),
|
|
218
|
+
__metadata("design:type", String)
|
|
219
|
+
], Appliances.prototype, "mode", void 0);
|
|
220
|
+
__decorate([
|
|
221
|
+
property({ type: Boolean }),
|
|
222
|
+
__metadata("design:type", Boolean)
|
|
223
|
+
], Appliances.prototype, "failed", void 0);
|
|
224
|
+
__decorate([
|
|
225
|
+
query('ox-grist'),
|
|
226
|
+
__metadata("design:type", Function)
|
|
227
|
+
], Appliances.prototype, "grist", void 0);
|
|
151
228
|
Appliances = __decorate([
|
|
152
229
|
customElement('appliance-home')
|
|
153
230
|
], Appliances);
|
|
231
|
+
export { Appliances };
|
|
154
232
|
//# 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":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,iCAAiC,CAAA;AACxC,OAAO,6CAA6C,CAAA;AAEpD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAGlE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAEpC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAE1E;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAA1D;;QAyBwB,WAAM,GAAY,KAAK,CAAA;QAExB,SAAI,GAA6B,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QAE/F,2EAA2E;QAC9C,WAAM,GAAY,KAAK,CAAA;IAyJtD,CAAC;aAtLQ,WAAM,GAAG;QACd,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;KAiBF;KACF,AAtBY,CAsBZ;IAWD,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC;YACnC,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE;oBAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;gBAChC,CAAC;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE;aACpC;YACD,OAAO,EAAE;gBACP;oBACE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC;oBACnC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;oBAC5C,IAAI,EAAE,KAAK;iBACZ;aACF;YACD,OAAO,EAAE,KAAK;SACf,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;gBAEC,IAAI,CAAC,IAAI;kBACP,IAAI,CAAC,MAAM;wBACL,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;kCAClB,IAAI,CAAC,yBAAyB,CAAC,UAAU,CAAE;;;8DAGf,IAAI,CAAC,OAAO;;;;QAIlE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,eAAe,OAAO,CAAC,CAAC,CAAC,mCAAmC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;KAC/F,CAAA;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,MAAM,GAAG;YACZ,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YAC5D,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC1C;oBACE,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,QAAQ;oBACpB,IAAI,EAAE,eAAe;oBACrB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;oBACjC,QAAQ,EAAE;wBACR,KAAK,EAAE,CAAC,QAAa,EAAE,KAAU,EAAE,OAAY,EAAE,MAAW,EAAE,EAAE;4BAC9D,IAAI,MAAM,EAAE,EAAE,EAAE,CAAC;gCACf,QAAQ,CAAC,aAAa,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;4BACpC,CAAC;wBACH,CAAC;qBACF;iBACF;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;oBAC/B,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,UAAU;oBAChB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC;oBACpC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;oBAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;oBAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;oBAClC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;oBACtC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,UAAU;oBAChB,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC;oBACrC,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;aACF;SACF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,QAAa,EAAE,UAAe;QAC9C,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,cAAc,CAAA;YACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAe;QAC1E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;OAgBT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;YAC7D,OAAO,EAAE,UAAU,EAAE;SACtB,CAAC,CAAA;QAEF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAA;QAEtF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAClC,CAAC;;AA7J4B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;0CAAwB;AACxB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CAAY;AACX;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wCAAoE;AAGlE;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;0CAAwB;AAEzB;IAA1B,KAAK,CAAC,UAAU,CAAC;;yCAA0B;AAhCjC,UAAU;IADtB,aAAa,CAAC,gBAAgB,CAAC;GACnB,UAAU,CAuLtB","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 { navigate, PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * 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: () => navigate('appliance-register'),\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 navigate(`appliance/${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 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"]}
|
|
@@ -6,7 +6,9 @@ import { css, html } from 'lit';
|
|
|
6
6
|
import { customElement, property, queryAll } from 'lit/decorators.js';
|
|
7
7
|
import { APPLICATION_TYPES } from '../../constants/application.js';
|
|
8
8
|
import { client } from '@operato/graphql';
|
|
9
|
+
import { i18next } from '@operato/i18n';
|
|
9
10
|
import { navigate, PageView } from '@operato/shell';
|
|
11
|
+
import '../../components/secret-field.js';
|
|
10
12
|
let Application = class Application extends PageView {
|
|
11
13
|
static { this.styles = [
|
|
12
14
|
css `
|
|
@@ -255,10 +257,15 @@ let Application = class Application extends PageView {
|
|
|
255
257
|
<button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>
|
|
256
258
|
</div>
|
|
257
259
|
|
|
260
|
+
<!--
|
|
261
|
+
appKey above stays a plain field: it is the client identifier, meant to be
|
|
262
|
+
read and quoted. appSecret is the other half — it carries its own
|
|
263
|
+
@privilege for security:query, and it is the value an integration signs
|
|
264
|
+
with. It was printed in the open from the moment the page loaded.
|
|
265
|
+
-->
|
|
258
266
|
<div field grid-span>
|
|
259
|
-
<label for="app-secret"
|
|
260
|
-
<
|
|
261
|
-
<button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>
|
|
267
|
+
<label for="app-secret">${i18next.t('field.client-secret')}</label>
|
|
268
|
+
<secret-field id="app-secret" .value=${app.appSecret || ''}></secret-field>
|
|
262
269
|
</div>
|
|
263
270
|
</div>
|
|
264
271
|
</fieldset>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application.js","sourceRoot":"","sources":["../../../client/pages/application/application.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport Clipboard from 'clipboard'\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, queryAll } from 'lit/decorators.js'\nimport { APPLICATION_TYPES } from '../../constants/application.js'\n\nimport { client } from '@operato/graphql'\nimport { navigate, PageView } from '@operato/shell'\n\n@customElement('application-page')\nclass Application extends PageView { static styles = [\n css`\n :host { display: flex;\n flex-direction: column;\n overflow-y: auto;\n position: relative;\n\n background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\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 [icon] { position: absolute;\n top: 10px;\n right: 10px;\n max-width: 80px;\n }\n\n [icon] img { max-width: 100%;\n max-height: 100%;\n }\n\n [button-primary] { background-color: var(--button-primary-background-color);\n border: var(--button-border);\n border-radius: var(--button-border-radius);\n margin: var(--button-margin);\n padding: var(--button-primary-padding);\n color: var(--button-primary-color);\n font: var(--button-primary-font);\n text-transform: var(--button-text-transform);\n\n text-decoration: none;\n }\n\n [button-primary]:hover { background-color: var(--button-primary-active-background-color);\n box-shadow: var(--button-active-box-shadow);\n }\n\n [fieldset-container] { background-color: var(--md-sys-color-surface);\n margin: var(--spacing-large) 0 var(--spacing-medium) 0;\n padding: var(--spacing-medium);\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n\n label { 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 select {\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 flex: 1;\n }\n\n select:focus,\n input:focus,\n button { outline: none;\n }\n\n form { max-width: var(--content-container-max-width);\n }\n }\n\n [fieldset-container] fieldset { margin: 0;\n margin-top: -15px;\n }\n\n fieldset { border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: var(--fieldset-margin);\n padding: var(--fieldset-padding);\n }\n\n legend { padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n [field-2column] { display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: 15px;\n }\n\n [field] { display: flex;\n flex-direction: column;\n position: relative;\n }\n\n [grid-span] { grid-column: span 2;\n }\n\n button { display: flex;\n align-items: center;\n gap: var(--spacing-small);\n }\n\n button,\n input[type='submit'],\n [button-in-field] { background-color: var(--button-background-color);\n border: var(--button-border);\n border-radius: var(--button-border-radius);\n padding: var(--button-padding);\n color: var(--button-color);\n font: var(--button-font);\n text-transform: var(--button-text-transform);\n\n margin: var(--spacing-medium) 0 var(--spacing-medium) var(--spacing-medium);\n float: right;\n text-decoration: none;\n }\n\n button:hover,\n input[type='submit']:hover { border: var(--button-activ-border);\n box-shadow: var(--button-active-box-shadow);\n }\n\n [button-in-field] { border-radius: 0 var(--button-border-radius) var(--button-border-radius) 0;\n position: absolute;\n top: 12px;\n right: 0;\n max-height: 36px;\n }\n\n [input-hint] { font: var(--input-hint-font);\n color: var(--input-hint-color);\n }\n\n @media screen and (max-width: 480px) { [field] { grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: Object }) application: any\n @queryAll('[clipboard-copy]') copybuttons\n private clipboard?: Clipboard\n\n get context() { return { title: { icon: 'apps',\n text: this.application.name\n }\n }\n }\n\n render() { var app = this.application || {}\n\n return html`\n <div>\n <h2><md-icon>apps</md-icon> ${app.name}</h2>\n <p page-description>${app.description}</p>\n </div>\n\n <div icon>\n <img src=${app.icon} />\n </div>\n\n <form>\n <div fieldset-container>\n <fieldset>\n <legend>application</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"name\">app name</label>\n <input id=\"name\" type=\"text\" name=\"name\" .value=${app.name} />\n </div>\n\n <div field grid-span>\n <label for=\"description\">description</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${app.description} />\n </div>\n\n <div field grid-span>\n <label for=\"email\">contact email</label>\n <input id=\"email\" type=\"text\" name=\"email\" .value=${app.email} />\n </div>\n\n <div field grid-span>\n <label for=\"type-selector\">type</label>\n <select id=\"type-selector\" name=\"type\">\n ${APPLICATION_TYPES.map(\n type =>\n html`<option value=\"${app.type}\" ?selected=\"${type === app.type ? true : false}\">${type}</option>`\n )}\n </select>\n </div>\n\n <div field grid-span>\n <label for=\"url\">application URL</label>\n <input id=\"url\" type=\"text\" name=\"url\" .value=${app.url} />\n </div>\n\n <div field grid-span>\n <label for=\"icon\">icon</label>\n <input id=\"icon\" type=\"text\" name=\"icon\" .value=${app.icon} />\n </div>\n\n <div field grid-span>\n <label for=\"redirect-url\">redirect URL</label>\n <input id=\"redirect-url\" type=\"text\" name=\"redirectUrl\" .value=${app.redirectUrl} />\n </div>\n\n <div field grid-span>\n <label for=\"webhook\">webhook</label>\n <input id=\"webhook\" type=\"text\" name=\"webhook\" .value=${app.webhook} />\n </div>\n\n <div field grid-span>\n <label for=\"available-scopes\">available-scopes</label>\n <input id=\"available-scopes\" type=\"text\" .value=${app.availableScopes} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"auth-url\">auth URL</label>\n <input id=\"auth-url\" type=\"text\" .value=${app.authUrl} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n The endpoint for authorization server. This is used to get the authorization code.\n </div>\n\n <div field grid-span>\n <label for=\"access-token-url\">access token URL</label>\n <input id=\"access-token-url\" type=\"text\" .value=${app.accessTokenUrl} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n The endpoint for authentication server. This is used to exchange the authorization code for an access\n token.\n </div>\n </div>\n </fieldset>\n </div>\n\n <div fieldset-container>\n <fieldset>\n <legend>credentials</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"app-key\">appKey</label>\n <input id=\"app-key\" type=\"text\" .value=${app.appKey} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n </div>\n\n <div field grid-span>\n <label for=\"app-secret\">appSecret</label>\n <input id=\"app-secret\" type=\"text\" .value=${app.appSecret} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n </div>\n </div>\n </fieldset>\n </div>\n\n <div class=\"button-container\">\n <button danger @click=${this.deleteApplication.bind(this)}>\n <md-icon>delete_forever</md-icon>delete this app\n </button>\n <button @click=${this.updateApplication.bind(this)}><md-icon>save</md-icon>update</button>\n <button><md-icon>passkey</md-icon>generate new secret</button>\n </div>\n </form>\n `\n }\n\n firstUpdated() { this.clipboard = new Clipboard(this.copybuttons, { target: (trigger => trigger.parentElement.querySelector('input')) as any\n })\n }\n\n async pageUpdated(changes, lifecycle, before) { if (this.active) { await this.fetchApplication()\n }\n }\n\n async fetchApplication() { const response = await client.query({ query: gql`\n query ($id: String!) { application(id: $id) { id\n name\n description\n email\n url\n icon\n redirectUrl\n authUrl\n accessTokenUrl\n webhook\n availableScopes\n appKey\n appSecret\n type\n }\n }\n `,\n variables: { id: this.lifecycle.resourceId\n }\n })\n\n this.application = response.data.application\n }\n\n async updateApplication(e) { e.preventDefault()\n\n const form = this.renderRoot.querySelector('form') as HTMLFormElement\n const formData = new FormData(form)\n\n const patch = Array.from(formData.entries()).reduce((patch, [key, value]) => { patch[key] = value\n return patch\n }, {})\n\n const id = this.lifecycle.resourceId\n\n const response = await client.mutate({ mutation: gql`\n mutation ($id: String!, $patch: ApplicationPatch!) { updateApplication(id: $id, patch: $patch) { id\n name\n description\n email\n url\n icon\n redirectUrl\n webhook\n appKey\n appSecret\n type\n }\n }\n `,\n variables: { id,\n patch\n }\n })\n\n if (response.errors) { console.error('update fail')\n } else { this.application = { ...this.application, ...response.data.updateApplication }\n console.log('update sucess')\n }\n }\n\n async deleteApplication(e) { e.preventDefault()\n\n const id = this.lifecycle.resourceId\n\n const response = await client.mutate({ mutation: gql`\n mutation ($id: String!) { deleteApplication(id: $id)\n }\n `,\n variables: { id\n }\n })\n\n const result = response.data.deleteApplication\n if (result) { console.log('delete sucess')\n navigate('applications')\n } else { console.error('delete fail')\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"application.js","sourceRoot":"","sources":["../../../client/pages/application/application.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport Clipboard from 'clipboard'\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, queryAll } from 'lit/decorators.js'\nimport { APPLICATION_TYPES } from '../../constants/application.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\n\nimport '../../components/secret-field.js'\n\n@customElement('application-page')\nclass Application extends PageView { static styles = [\n css`\n :host { display: flex;\n flex-direction: column;\n overflow-y: auto;\n position: relative;\n\n background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\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 [icon] { position: absolute;\n top: 10px;\n right: 10px;\n max-width: 80px;\n }\n\n [icon] img { max-width: 100%;\n max-height: 100%;\n }\n\n [button-primary] { background-color: var(--button-primary-background-color);\n border: var(--button-border);\n border-radius: var(--button-border-radius);\n margin: var(--button-margin);\n padding: var(--button-primary-padding);\n color: var(--button-primary-color);\n font: var(--button-primary-font);\n text-transform: var(--button-text-transform);\n\n text-decoration: none;\n }\n\n [button-primary]:hover { background-color: var(--button-primary-active-background-color);\n box-shadow: var(--button-active-box-shadow);\n }\n\n [fieldset-container] { background-color: var(--md-sys-color-surface);\n margin: var(--spacing-large) 0 var(--spacing-medium) 0;\n padding: var(--spacing-medium);\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n\n label { 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 select {\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 flex: 1;\n }\n\n select:focus,\n input:focus,\n button { outline: none;\n }\n\n form { max-width: var(--content-container-max-width);\n }\n }\n\n [fieldset-container] fieldset { margin: 0;\n margin-top: -15px;\n }\n\n fieldset { border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: var(--fieldset-margin);\n padding: var(--fieldset-padding);\n }\n\n legend { padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n [field-2column] { display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: 15px;\n }\n\n [field] { display: flex;\n flex-direction: column;\n position: relative;\n }\n\n [grid-span] { grid-column: span 2;\n }\n\n button { display: flex;\n align-items: center;\n gap: var(--spacing-small);\n }\n\n button,\n input[type='submit'],\n [button-in-field] { background-color: var(--button-background-color);\n border: var(--button-border);\n border-radius: var(--button-border-radius);\n padding: var(--button-padding);\n color: var(--button-color);\n font: var(--button-font);\n text-transform: var(--button-text-transform);\n\n margin: var(--spacing-medium) 0 var(--spacing-medium) var(--spacing-medium);\n float: right;\n text-decoration: none;\n }\n\n button:hover,\n input[type='submit']:hover { border: var(--button-activ-border);\n box-shadow: var(--button-active-box-shadow);\n }\n\n [button-in-field] { border-radius: 0 var(--button-border-radius) var(--button-border-radius) 0;\n position: absolute;\n top: 12px;\n right: 0;\n max-height: 36px;\n }\n\n [input-hint] { font: var(--input-hint-font);\n color: var(--input-hint-color);\n }\n\n @media screen and (max-width: 480px) { [field] { grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: Object }) application: any\n @queryAll('[clipboard-copy]') copybuttons\n private clipboard?: Clipboard\n\n get context() { return { title: { icon: 'apps',\n text: this.application.name\n }\n }\n }\n\n render() { var app = this.application || {}\n\n return html`\n <div>\n <h2><md-icon>apps</md-icon> ${app.name}</h2>\n <p page-description>${app.description}</p>\n </div>\n\n <div icon>\n <img src=${app.icon} />\n </div>\n\n <form>\n <div fieldset-container>\n <fieldset>\n <legend>application</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"name\">app name</label>\n <input id=\"name\" type=\"text\" name=\"name\" .value=${app.name} />\n </div>\n\n <div field grid-span>\n <label for=\"description\">description</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${app.description} />\n </div>\n\n <div field grid-span>\n <label for=\"email\">contact email</label>\n <input id=\"email\" type=\"text\" name=\"email\" .value=${app.email} />\n </div>\n\n <div field grid-span>\n <label for=\"type-selector\">type</label>\n <select id=\"type-selector\" name=\"type\">\n ${APPLICATION_TYPES.map(\n type =>\n html`<option value=\"${app.type}\" ?selected=\"${type === app.type ? true : false}\">${type}</option>`\n )}\n </select>\n </div>\n\n <div field grid-span>\n <label for=\"url\">application URL</label>\n <input id=\"url\" type=\"text\" name=\"url\" .value=${app.url} />\n </div>\n\n <div field grid-span>\n <label for=\"icon\">icon</label>\n <input id=\"icon\" type=\"text\" name=\"icon\" .value=${app.icon} />\n </div>\n\n <div field grid-span>\n <label for=\"redirect-url\">redirect URL</label>\n <input id=\"redirect-url\" type=\"text\" name=\"redirectUrl\" .value=${app.redirectUrl} />\n </div>\n\n <div field grid-span>\n <label for=\"webhook\">webhook</label>\n <input id=\"webhook\" type=\"text\" name=\"webhook\" .value=${app.webhook} />\n </div>\n\n <div field grid-span>\n <label for=\"available-scopes\">available-scopes</label>\n <input id=\"available-scopes\" type=\"text\" .value=${app.availableScopes} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"auth-url\">auth URL</label>\n <input id=\"auth-url\" type=\"text\" .value=${app.authUrl} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n The endpoint for authorization server. This is used to get the authorization code.\n </div>\n\n <div field grid-span>\n <label for=\"access-token-url\">access token URL</label>\n <input id=\"access-token-url\" type=\"text\" .value=${app.accessTokenUrl} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n The endpoint for authentication server. This is used to exchange the authorization code for an access\n token.\n </div>\n </div>\n </fieldset>\n </div>\n\n <div fieldset-container>\n <fieldset>\n <legend>credentials</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"app-key\">appKey</label>\n <input id=\"app-key\" type=\"text\" .value=${app.appKey} readonly />\n <button dense button-in-field clipboard-copy @click=${e => e.preventDefault()}>copy</button>\n </div>\n\n <!--\n appKey above stays a plain field: it is the client identifier, meant to be\n read and quoted. appSecret is the other half — it carries its own\n @privilege for security:query, and it is the value an integration signs\n with. It was printed in the open from the moment the page loaded.\n -->\n <div field grid-span>\n <label for=\"app-secret\">${i18next.t('field.client-secret')}</label>\n <secret-field id=\"app-secret\" .value=${app.appSecret || ''}></secret-field>\n </div>\n </div>\n </fieldset>\n </div>\n\n <div class=\"button-container\">\n <button danger @click=${this.deleteApplication.bind(this)}>\n <md-icon>delete_forever</md-icon>delete this app\n </button>\n <button @click=${this.updateApplication.bind(this)}><md-icon>save</md-icon>update</button>\n <button><md-icon>passkey</md-icon>generate new secret</button>\n </div>\n </form>\n `\n }\n\n firstUpdated() { this.clipboard = new Clipboard(this.copybuttons, { target: (trigger => trigger.parentElement.querySelector('input')) as any\n })\n }\n\n async pageUpdated(changes, lifecycle, before) { if (this.active) { await this.fetchApplication()\n }\n }\n\n async fetchApplication() { const response = await client.query({ query: gql`\n query ($id: String!) { application(id: $id) { id\n name\n description\n email\n url\n icon\n redirectUrl\n authUrl\n accessTokenUrl\n webhook\n availableScopes\n appKey\n appSecret\n type\n }\n }\n `,\n variables: { id: this.lifecycle.resourceId\n }\n })\n\n this.application = response.data.application\n }\n\n async updateApplication(e) { e.preventDefault()\n\n const form = this.renderRoot.querySelector('form') as HTMLFormElement\n const formData = new FormData(form)\n\n const patch = Array.from(formData.entries()).reduce((patch, [key, value]) => { patch[key] = value\n return patch\n }, {})\n\n const id = this.lifecycle.resourceId\n\n const response = await client.mutate({ mutation: gql`\n mutation ($id: String!, $patch: ApplicationPatch!) { updateApplication(id: $id, patch: $patch) { id\n name\n description\n email\n url\n icon\n redirectUrl\n webhook\n appKey\n appSecret\n type\n }\n }\n `,\n variables: { id,\n patch\n }\n })\n\n if (response.errors) { console.error('update fail')\n } else { this.application = { ...this.application, ...response.data.updateApplication }\n console.log('update sucess')\n }\n }\n\n async deleteApplication(e) { e.preventDefault()\n\n const id = this.lifecycle.resourceId\n\n const response = await client.mutate({ mutation: gql`\n mutation ($id: String!) { deleteApplication(id: $id)\n }\n `,\n variables: { id\n }\n })\n\n const result = response.data.deleteApplication\n if (result) { console.log('delete sucess')\n navigate('applications')\n } else { console.error('delete fail')\n }\n }\n}\n"]}
|
|
@@ -1,2 +1,61 @@
|
|
|
1
|
-
import '@material/web/
|
|
2
|
-
import '@
|
|
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
|
+
declare const Applications_base: (new (...args: any[]) => {
|
|
7
|
+
__preferenceProviders: {
|
|
8
|
+
[element: string]: import("@operato/p13n").PagePreferenceProvider;
|
|
9
|
+
};
|
|
10
|
+
getPagePreferenceProvider(element: string): import("@operato/p13n").PagePreferenceProvider | undefined;
|
|
11
|
+
}) & (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
12
|
+
/**
|
|
13
|
+
* Registered applications.
|
|
14
|
+
*
|
|
15
|
+
* ── What this screen looked like before ──────────────────────────────────────
|
|
16
|
+
* A hand-written `<table>` with no paging, no sort and no search, and three columns that
|
|
17
|
+
* were not data at all: "API health" printed OK, "Installs" printed 1, "status" printed
|
|
18
|
+
* draft — written into the markup, identical on every row. The previous commit removed the
|
|
19
|
+
* two the server cannot answer and made status real. This one gives the list the same shape
|
|
20
|
+
* as every other list in the product.
|
|
21
|
+
*
|
|
22
|
+
* The server was already ready for it: `applications` takes ListParam and declares
|
|
23
|
+
* `searchables: ['name', 'description']`, so paging, sorting and the header's search box all
|
|
24
|
+
* work by passing the grist's own options through.
|
|
25
|
+
*
|
|
26
|
+
* ── Why the row does not carry health or install counts ──────────────────────
|
|
27
|
+
* Because Application does not declare them. If they arrive one day, they are columns; until
|
|
28
|
+
* then the screen says what it knows. See `utils/application-status.ts` for the matching rule
|
|
29
|
+
* on values the screen has no word for.
|
|
30
|
+
*/
|
|
31
|
+
export declare class Applications extends Applications_base {
|
|
32
|
+
static styles: import("lit").CSSResult[];
|
|
33
|
+
active: boolean;
|
|
34
|
+
config: any;
|
|
35
|
+
mode: 'CARD' | 'LIST' | 'GRID';
|
|
36
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
37
|
+
failed: boolean;
|
|
38
|
+
private grist;
|
|
39
|
+
get context(): {
|
|
40
|
+
title: string;
|
|
41
|
+
help: string;
|
|
42
|
+
search: {
|
|
43
|
+
handler: (search: string) => void;
|
|
44
|
+
value: string;
|
|
45
|
+
};
|
|
46
|
+
actions: {
|
|
47
|
+
title: string;
|
|
48
|
+
action: () => void;
|
|
49
|
+
icon: string;
|
|
50
|
+
}[];
|
|
51
|
+
toolbar: boolean;
|
|
52
|
+
};
|
|
53
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
54
|
+
pageInitialized(): Promise<void>;
|
|
55
|
+
pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
|
|
56
|
+
fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
|
|
57
|
+
total: number;
|
|
58
|
+
records: any[];
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
export {};
|