@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,160 +1,225 @@
|
|
|
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 { applicationStatusLabel } from '../../utils/application-status.js';
|
|
15
|
+
import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
|
|
16
|
+
/**
|
|
17
|
+
* Registered applications.
|
|
18
|
+
*
|
|
19
|
+
* ── What this screen looked like before ──────────────────────────────────────
|
|
20
|
+
* A hand-written `<table>` with no paging, no sort and no search, and three columns that
|
|
21
|
+
* were not data at all: "API health" printed OK, "Installs" printed 1, "status" printed
|
|
22
|
+
* draft — written into the markup, identical on every row. The previous commit removed the
|
|
23
|
+
* two the server cannot answer and made status real. This one gives the list the same shape
|
|
24
|
+
* as every other list in the product.
|
|
25
|
+
*
|
|
26
|
+
* The server was already ready for it: `applications` takes ListParam and declares
|
|
27
|
+
* `searchables: ['name', 'description']`, so paging, sorting and the header's search box all
|
|
28
|
+
* work by passing the grist's own options through.
|
|
29
|
+
*
|
|
30
|
+
* ── Why the row does not carry health or install counts ──────────────────────
|
|
31
|
+
* Because Application does not declare them. If they arrive one day, they are columns; until
|
|
32
|
+
* then the screen says what it knows. See `utils/application-status.ts` for the matching rule
|
|
33
|
+
* on values the screen has no word for.
|
|
34
|
+
*/
|
|
35
|
+
let Applications = class Applications extends p13n(localize(i18next)(PageView)) {
|
|
10
36
|
constructor() {
|
|
11
37
|
super(...arguments);
|
|
12
|
-
this.
|
|
38
|
+
this.active = false;
|
|
39
|
+
this.mode = isMobileDevice() ? 'CARD' : 'GRID';
|
|
40
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
41
|
+
this.failed = false;
|
|
13
42
|
}
|
|
14
43
|
static { this.styles = [
|
|
44
|
+
CommonGristStyles,
|
|
45
|
+
CommonHeaderStyles,
|
|
46
|
+
ScrollbarStyles,
|
|
15
47
|
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 { margin: var(--spacing-large) 0;
|
|
41
|
-
width: 100%;
|
|
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 { font: var(--td-font);
|
|
71
|
-
text-decoration: none;
|
|
72
|
-
color: var(--md-sys-color-on-surface);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
td a strong { font: bold 16px var(--theme-font);
|
|
76
|
-
|
|
77
|
-
display: block;
|
|
78
|
-
text-decoration: none;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.text-align-center { text-align: center;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.text-align-right { text-align: right;
|
|
85
|
-
}
|
|
48
|
+
:host {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ox-grist {
|
|
55
|
+
overflow-y: auto;
|
|
56
|
+
flex: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[failed] {
|
|
60
|
+
padding: var(--spacing-medium);
|
|
61
|
+
color: var(--md-sys-color-error);
|
|
62
|
+
font: var(--label-font, inherit);
|
|
63
|
+
}
|
|
86
64
|
`
|
|
87
65
|
]; }
|
|
88
66
|
get context() {
|
|
89
|
-
return {
|
|
90
|
-
|
|
67
|
+
return {
|
|
68
|
+
title: i18next.t('text.applications'),
|
|
69
|
+
help: 'auth/application',
|
|
70
|
+
search: {
|
|
71
|
+
handler: (search) => {
|
|
72
|
+
this.grist.searchText = search;
|
|
73
|
+
},
|
|
74
|
+
value: this.grist?.searchText || ''
|
|
75
|
+
},
|
|
76
|
+
actions: [
|
|
77
|
+
{
|
|
78
|
+
title: i18next.t('button.register'),
|
|
79
|
+
action: () => navigate('application-register'),
|
|
80
|
+
icon: 'add'
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
toolbar: false
|
|
91
84
|
};
|
|
92
85
|
}
|
|
93
86
|
render() {
|
|
94
|
-
var apps = this.applications || [];
|
|
95
87
|
return html `
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
>
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<tr>
|
|
109
|
-
<th>app name</th>
|
|
110
|
-
<th>API health</th>
|
|
111
|
-
<th>Installs</th>
|
|
112
|
-
<th>status</th>
|
|
113
|
-
</tr>
|
|
114
|
-
${apps.map(app => html `
|
|
115
|
-
<tr>
|
|
116
|
-
<td>
|
|
117
|
-
<a href=${`application/${app.id}`}>
|
|
118
|
-
<strong>${app.name}</strong>
|
|
119
|
-
${app.description}
|
|
120
|
-
</a>
|
|
121
|
-
</td>
|
|
122
|
-
<td>OK</td>
|
|
123
|
-
<td class="text-align-center">1</td>
|
|
124
|
-
<td>draft</td>
|
|
125
|
-
</tr>
|
|
126
|
-
`)}
|
|
127
|
-
</table>
|
|
128
|
-
|
|
129
|
-
<md-outlined-button @click=${e => navigate('app-bindings')}>bound applications ..</md-outlined-button>
|
|
88
|
+
<ox-grist
|
|
89
|
+
.mode=${this.mode}
|
|
90
|
+
.config=${this.config}
|
|
91
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
92
|
+
.personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
|
|
93
|
+
>
|
|
94
|
+
<div slot="headroom" class="header">
|
|
95
|
+
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
96
|
+
</div>
|
|
97
|
+
</ox-grist>
|
|
98
|
+
|
|
99
|
+
${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
|
|
130
100
|
`;
|
|
131
101
|
}
|
|
132
|
-
async
|
|
102
|
+
async pageInitialized() {
|
|
103
|
+
this.config = {
|
|
104
|
+
rows: { appendable: false, selectable: { multiple: false } },
|
|
105
|
+
columns: [
|
|
106
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
107
|
+
{
|
|
108
|
+
type: 'gutter',
|
|
109
|
+
gutterName: 'button',
|
|
110
|
+
icon: 'chevron_right',
|
|
111
|
+
title: i18next.t('button.detail'),
|
|
112
|
+
handlers: {
|
|
113
|
+
click: (_columns, _data, _column, record) => {
|
|
114
|
+
if (record?.id) {
|
|
115
|
+
navigate(`application/${record.id}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'string',
|
|
122
|
+
name: 'name',
|
|
123
|
+
header: i18next.t('field.name'),
|
|
124
|
+
record: { editable: false },
|
|
125
|
+
sortable: true,
|
|
126
|
+
width: 200
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'string',
|
|
130
|
+
name: 'description',
|
|
131
|
+
header: i18next.t('field.description'),
|
|
132
|
+
record: { editable: false },
|
|
133
|
+
width: 260
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'string',
|
|
137
|
+
name: 'type',
|
|
138
|
+
header: i18next.t('field.type'),
|
|
139
|
+
record: { align: 'center', editable: false },
|
|
140
|
+
sortable: true,
|
|
141
|
+
width: 120
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'string',
|
|
145
|
+
name: 'statusLabel',
|
|
146
|
+
header: i18next.t('field.status'),
|
|
147
|
+
record: { align: 'center', editable: false },
|
|
148
|
+
width: 120
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'datetime',
|
|
152
|
+
name: 'updatedAt',
|
|
153
|
+
header: i18next.t('field.updated at'),
|
|
154
|
+
sortable: true,
|
|
155
|
+
width: 180
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async pageUpdated(_changes, _lifecycle) {
|
|
133
161
|
if (this.active) {
|
|
134
|
-
|
|
162
|
+
await this.updateComplete;
|
|
163
|
+
this.grist.fetch();
|
|
135
164
|
}
|
|
136
165
|
}
|
|
137
|
-
async
|
|
138
|
-
const response = await client.query({
|
|
139
|
-
|
|
166
|
+
async fetchHandler({ page, limit, sortings = [], filters = [] }) {
|
|
167
|
+
const response = await client.query({
|
|
168
|
+
query: gql `
|
|
169
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
170
|
+
applications(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
171
|
+
items {
|
|
172
|
+
id
|
|
140
173
|
name
|
|
141
174
|
description
|
|
142
|
-
|
|
175
|
+
type
|
|
176
|
+
status
|
|
177
|
+
updatedAt
|
|
178
|
+
}
|
|
143
179
|
total
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
`
|
|
147
|
-
});
|
|
148
|
-
if (!response.errors) {
|
|
149
|
-
return response.data.applications;
|
|
180
|
+
}
|
|
150
181
|
}
|
|
182
|
+
`,
|
|
183
|
+
variables: { filters, pagination: { page, limit }, sortings },
|
|
184
|
+
context: gqlContext()
|
|
185
|
+
});
|
|
186
|
+
const { items, total, failed } = listOutcome(pageFromResponse(response, 'applications'));
|
|
187
|
+
this.failed = failed;
|
|
188
|
+
/*
|
|
189
|
+
* The status is turned into words here rather than in a cell renderer so the rule lives
|
|
190
|
+
* in one tested place — a value this screen has no word for is printed as it arrived.
|
|
191
|
+
*/
|
|
192
|
+
return {
|
|
193
|
+
total,
|
|
194
|
+
records: items.map((item) => ({
|
|
195
|
+
...item,
|
|
196
|
+
statusLabel: applicationStatusLabel(item.status, (key) => i18next.t(key))
|
|
197
|
+
}))
|
|
198
|
+
};
|
|
151
199
|
}
|
|
152
200
|
};
|
|
153
201
|
__decorate([
|
|
154
|
-
property({ type:
|
|
155
|
-
__metadata("design:type",
|
|
156
|
-
], Applications.prototype, "
|
|
202
|
+
property({ type: Boolean }),
|
|
203
|
+
__metadata("design:type", Boolean)
|
|
204
|
+
], Applications.prototype, "active", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
property({ type: Object }),
|
|
207
|
+
__metadata("design:type", Object)
|
|
208
|
+
], Applications.prototype, "config", void 0);
|
|
209
|
+
__decorate([
|
|
210
|
+
property({ type: String }),
|
|
211
|
+
__metadata("design:type", String)
|
|
212
|
+
], Applications.prototype, "mode", void 0);
|
|
213
|
+
__decorate([
|
|
214
|
+
property({ type: Boolean }),
|
|
215
|
+
__metadata("design:type", Boolean)
|
|
216
|
+
], Applications.prototype, "failed", void 0);
|
|
217
|
+
__decorate([
|
|
218
|
+
query('ox-grist'),
|
|
219
|
+
__metadata("design:type", Function)
|
|
220
|
+
], Applications.prototype, "grist", void 0);
|
|
157
221
|
Applications = __decorate([
|
|
158
222
|
customElement('applications-page')
|
|
159
223
|
], Applications);
|
|
224
|
+
export { Applications };
|
|
160
225
|
//# sourceMappingURL=applications.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applications.js","sourceRoot":"","sources":["../../../client/pages/application/applications.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,YAAY,GAAlB,MAAM,YAAa,SAAQ,QAAQ;IAAnC;;QA2E6B,iBAAY,GAAU,EAAE,CAAA;IAmErD,CAAC;aA9I8C,WAAM,GAAG;QACpD,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuEF;KACF,AAzEkD,CAyElD;IAID,IAAI,OAAO;QAAS,OAAO,EAAQ,KAAK,EAAE,cAAc;YACpD,IAAI,EAAE,kBAAkB;SAC5B,CAAA;IACD,CAAC;IAEA,MAAM;QAAS,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAA;QAE/C,OAAO,IAAI,CAAA;;;;;;;qCAOsB,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;;;;;;;;;;;;UAYhE,IAAI,CAAC,GAAG,CACR,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA;;;0BAGK,eAAe,GAAG,CAAC,EAAE,EAAE;4BACrB,GAAG,CAAC,IAAI;oBAChB,GAAG,CAAC,WAAW;;;;;;;WAOxB,CACF;;;mCAG0B,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;KAC3D,CAAA;IACJ,CAAC;IAEA,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM;QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAAO,IAAI,CAAC,YAAY,GAAG,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAA;QACxI,CAAC;IACD,CAAC;IAEA,KAAK,CAAC,iBAAiB;QAAS,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAQ,KAAK,EAAE,GAAG,CAAA;;;;;;;;OAQjF;SACL,CAAC,CAAA;QAEC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAAO,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAA;QAClE,CAAC;IACD,CAAC;;AAlE2B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;kDAAyB;AA3E/C,YAAY;IADjB,aAAa,CAAC,mBAAmB,CAAC;GAC7B,YAAY,CA8IjB","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('applications-page')\nclass Applications 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 { margin: var(--spacing-large) 0;\n width: 100%;\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 { font: var(--td-font);\n text-decoration: none;\n color: var(--md-sys-color-on-surface);\n }\n\n td a strong { 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 ]\n\n @property({ type: Array }) applications: any[] = []\n\n get context() { return { title: `applications`,\n help: 'auth/application'\n }\n }\n\n render() { var apps = this.applications || []\n\n return html`\n <div>\n <h2>Registered Applications</h2>\n <p page-description>\n What type of app are you building?<br />Choose the app type that best suits the audience you’re building for.\n </p>\n\n <md-elevated-button @click=${e => navigate('application-register')}\n >register new application</md-elevated-button\n >\n </div>\n\n <table>\n <tr>\n <th>app name</th>\n <th>API health</th>\n <th>Installs</th>\n <th>status</th>\n </tr>\n ${apps.map(\n app => html`\n <tr>\n <td>\n <a href=${`application/${app.id}`}>\n <strong>${app.name}</strong>\n ${app.description}\n </a>\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 <md-outlined-button @click=${e => navigate('app-bindings')}>bound applications ..</md-outlined-button>\n `\n }\n\n async pageUpdated(changes, lifecycle, before) { if (this.active) { this.applications = (await this.fetchApplications()).items\n }\n }\n\n async fetchApplications() { const response = await client.query({ query: gql`\n query { applications { items { id\n name\n description\n }\n total\n }\n }\n `\n })\n\n if (!response.errors) { return response.data.applications\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"applications.js","sourceRoot":"","sources":["../../../client/pages/application/applications.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 { navigate, PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport { applicationStatusLabel } from '../../utils/application-status.js'\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Registered applications.\n *\n * ── What this screen looked like before ──────────────────────────────────────\n * A hand-written `<table>` with no paging, no sort and no search, and three columns that\n * were not data at all: \"API health\" printed OK, \"Installs\" printed 1, \"status\" printed\n * draft — written into the markup, identical on every row. The previous commit removed the\n * two the server cannot answer and made status real. This one gives the list the same shape\n * as every other list in the product.\n *\n * The server was already ready for it: `applications` takes ListParam and declares\n * `searchables: ['name', 'description']`, so paging, sorting and the header's search box all\n * work by passing the grist's own options through.\n *\n * ── Why the row does not carry health or install counts ──────────────────────\n * Because Application does not declare them. If they arrive one day, they are columns; until\n * then the screen says what it knows. See `utils/application-status.ts` for the matching rule\n * on values the screen has no word for.\n */\n@customElement('applications-page')\nexport class Applications 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.applications'),\n help: 'auth/application',\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('application-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(`application/${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: 200\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n width: 260\n },\n {\n type: 'string',\n name: 'type',\n header: i18next.t('field.type'),\n record: { align: 'center', editable: false },\n sortable: true,\n width: 120\n },\n {\n type: 'string',\n name: 'statusLabel',\n header: i18next.t('field.status'),\n record: { align: 'center', editable: false },\n width: 120\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 applications(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n name\n description\n type\n status\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, 'applications'))\n\n this.failed = failed\n\n /*\n * The status is turned into words here rather than in a cell renderer so the rule lives\n * in one tested place — a value this screen has no word for is printed as it arrived.\n */\n return {\n total,\n records: items.map((item: any) => ({\n ...item,\n statusLabel: applicationStatusLabel(item.status, (key: string) => i18next.t(key))\n }))\n }\n }\n}\n"]}
|
|
@@ -3,9 +3,20 @@ import gql from 'graphql-tag';
|
|
|
3
3
|
import { css, html } from 'lit';
|
|
4
4
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
5
5
|
import { client } from '@operato/graphql';
|
|
6
|
+
import { i18next, localize } from '@operato/i18n';
|
|
6
7
|
import { navigate, PageView } from '@operato/shell';
|
|
7
|
-
|
|
8
|
+
import { FormFieldStyles } from '@operato/styles';
|
|
9
|
+
import { filledValues, missingRequired } from '../../utils/form-values.js';
|
|
10
|
+
let ApplicationRegister = class ApplicationRegister extends localize(i18next)(PageView) {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
/** Required fields the person has not filled in yet. Empty until they press register. */
|
|
14
|
+
this.missing = [];
|
|
15
|
+
/** The mutation was refused. The link already said why; this says the press landed. */
|
|
16
|
+
this.failed = false;
|
|
17
|
+
}
|
|
8
18
|
static { this.styles = [
|
|
19
|
+
FormFieldStyles,
|
|
9
20
|
css `
|
|
10
21
|
:host { background-color: var(--md-sys-color-background);
|
|
11
22
|
padding: var(--spacing-large);
|
|
@@ -22,6 +33,15 @@ let ApplicationRegister = class ApplicationRegister extends PageView {
|
|
|
22
33
|
font: var(--page-description-font);
|
|
23
34
|
color: var(--page-description-color);
|
|
24
35
|
}
|
|
36
|
+
[field-error],
|
|
37
|
+
[form-error] { color: var(--md-sys-color-error);
|
|
38
|
+
font: var(--input-hint-font, inherit);
|
|
39
|
+
margin-top: var(--spacing-tiny, 2px);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
input[invalid] { border-color: var(--md-sys-color-error);
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
[icon] { position: absolute;
|
|
26
46
|
top: 10px;
|
|
27
47
|
right: 10px;
|
|
@@ -92,11 +112,8 @@ let ApplicationRegister = class ApplicationRegister extends PageView {
|
|
|
92
112
|
render() {
|
|
93
113
|
return html `
|
|
94
114
|
<img src="/assets/images/image-api.png" pagedeco />
|
|
95
|
-
<h2
|
|
96
|
-
<p page-description>
|
|
97
|
-
You can register new application here.<br />
|
|
98
|
-
What type of app are you building? Choose the app type that best suits the audience you’re building for.
|
|
99
|
-
</p>
|
|
115
|
+
<h2>${i18next.t('text.register new application')}</h2>
|
|
116
|
+
<p page-description>${i18next.t('text.an application signs in on its own behalf')}</p>
|
|
100
117
|
|
|
101
118
|
${this._icon
|
|
102
119
|
? html `
|
|
@@ -110,36 +127,44 @@ let ApplicationRegister = class ApplicationRegister extends PageView {
|
|
|
110
127
|
<form>
|
|
111
128
|
<div field-2column>
|
|
112
129
|
<div field grid-span>
|
|
113
|
-
<label for="name"
|
|
114
|
-
<input id="name" type="text" name="name" />
|
|
130
|
+
<label for="name">${i18next.t('field.name')}</label>
|
|
131
|
+
<input id="name" type="text" name="name" required ?invalid=${this.missing.includes('name')} />
|
|
132
|
+
${this.missing.includes('name')
|
|
133
|
+
? html `<div field-error>${i18next.t('text.this field is required')}</div>`
|
|
134
|
+
: ''}
|
|
115
135
|
</div>
|
|
116
136
|
|
|
117
137
|
<div field grid-span>
|
|
118
|
-
<label for="description"
|
|
138
|
+
<label for="description">${i18next.t('field.description')}</label>
|
|
119
139
|
<input id="description" type="text" name="description" />
|
|
120
140
|
</div>
|
|
121
141
|
|
|
122
142
|
<div field grid-span>
|
|
123
|
-
<label for="app-url"
|
|
124
|
-
<input id="app-url" type="
|
|
143
|
+
<label for="app-url">${i18next.t('field.application url')}</label>
|
|
144
|
+
<input id="app-url" type="url" name="url" />
|
|
125
145
|
</div>
|
|
126
146
|
|
|
127
147
|
<div field grid-span>
|
|
128
|
-
<label for="redirect-url"
|
|
129
|
-
<input id="redirect-url" type="
|
|
148
|
+
<label for="redirect-url">${i18next.t('field.redirect url')}</label>
|
|
149
|
+
<input id="redirect-url" type="url" name="redirectUrl" />
|
|
130
150
|
</div>
|
|
131
151
|
|
|
132
152
|
<div field grid-span>
|
|
133
|
-
<label for="icon"
|
|
134
|
-
<input id="icon" type="
|
|
153
|
+
<label for="icon">${i18next.t('field.application icon')}</label>
|
|
154
|
+
<input id="icon" type="url" name="icon" @change=${e => (this._icon = e.target.value)} />
|
|
135
155
|
</div>
|
|
136
156
|
|
|
137
157
|
<div field grid-span>
|
|
138
|
-
|
|
139
|
-
<
|
|
158
|
+
<!-- type=email so the browser checks the shape the server's scalar will check. -->
|
|
159
|
+
<label for="email">${i18next.t('field.contact email')}</label>
|
|
160
|
+
<input id="email" type="email" name="email" />
|
|
140
161
|
</div>
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
${this.failed ? html `<div form-error grid-span>${i18next.t('text.could not register')}</div>` : ''}
|
|
164
|
+
|
|
165
|
+
<button grid-span type="submit" @click=${this.createApplication.bind(this)}>
|
|
166
|
+
${i18next.t('button.register')}
|
|
167
|
+
</button>
|
|
143
168
|
</div>
|
|
144
169
|
</form>
|
|
145
170
|
</div>
|
|
@@ -148,10 +173,27 @@ let ApplicationRegister = class ApplicationRegister extends PageView {
|
|
|
148
173
|
async createApplication(e) {
|
|
149
174
|
e.preventDefault();
|
|
150
175
|
const formData = new FormData(this.form);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
176
|
+
/*
|
|
177
|
+
`name` is the only field NewApplication declares non-null. The list comes from the
|
|
178
|
+
schema rather than from taste — requiring more here would invent a rule the product
|
|
179
|
+
does not have, and the person would be stopped by this screen alone.
|
|
180
|
+
*/
|
|
181
|
+
const missing = missingRequired(formData, ['name']);
|
|
182
|
+
if (missing.length) {
|
|
183
|
+
this.missing = missing;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.missing = [];
|
|
187
|
+
/*
|
|
188
|
+
This used to send every input, including the untouched ones, each as an empty string.
|
|
189
|
+
`NewApplication.email` is a GraphQLEmailAddress and rejects '' — so registering without
|
|
190
|
+
a contact email failed, on a field the schema marks optional, and the screen printed
|
|
191
|
+
"creation fail." to the console where nobody would see it.
|
|
192
|
+
|
|
193
|
+
`filledValues` leaves an empty field out instead, which is what optional means on the
|
|
194
|
+
wire.
|
|
195
|
+
*/
|
|
196
|
+
const application = filledValues(formData);
|
|
155
197
|
const response = await client.mutate({ mutation: gql `
|
|
156
198
|
mutation ($application: NewApplication!) { createApplication(application: $application) { id
|
|
157
199
|
}
|
|
@@ -161,12 +203,16 @@ let ApplicationRegister = class ApplicationRegister extends PageView {
|
|
|
161
203
|
}
|
|
162
204
|
});
|
|
163
205
|
if (response.errors) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
206
|
+
/*
|
|
207
|
+
The shared client link already raises a notify event carrying the server's reason, so
|
|
208
|
+
the person sees what went wrong. What was missing is that the screen stayed silent
|
|
209
|
+
and looked idle — the form is still filled in, and nothing said the press had landed.
|
|
210
|
+
*/
|
|
211
|
+
this.failed = true;
|
|
212
|
+
return;
|
|
169
213
|
}
|
|
214
|
+
this.failed = false;
|
|
215
|
+
navigate(`application/${response.data.createApplication.id}`);
|
|
170
216
|
}
|
|
171
217
|
pageUpdated(changes, lifecycle, before) {
|
|
172
218
|
if (this.active) {
|
|
@@ -186,6 +232,14 @@ __decorate([
|
|
|
186
232
|
state(),
|
|
187
233
|
__metadata("design:type", String)
|
|
188
234
|
], ApplicationRegister.prototype, "_icon", void 0);
|
|
235
|
+
__decorate([
|
|
236
|
+
state(),
|
|
237
|
+
__metadata("design:type", Array)
|
|
238
|
+
], ApplicationRegister.prototype, "missing", void 0);
|
|
239
|
+
__decorate([
|
|
240
|
+
state(),
|
|
241
|
+
__metadata("design:type", Boolean)
|
|
242
|
+
], ApplicationRegister.prototype, "failed", void 0);
|
|
189
243
|
ApplicationRegister = __decorate([
|
|
190
244
|
customElement('application-register')
|
|
191
245
|
], ApplicationRegister);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../client/pages/application/register.ts"],"names":[],"mappings":";AAAA,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,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGnD,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,QAAQ;aAAY,WAAM,GAAG;QAC3D,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4EF;KACF,AA9EyD,CA8EzD;IAQD,IAAI,OAAO;QAAS,OAAO,EAAQ,KAAK,EAAE,0BAA0B;YAChE,IAAI,EAAE,kBAAkB;SAC5B,CAAA;IACD,CAAC;IAEA,MAAM;QAAS,OAAO,IAAI,CAAA;;;;;;;;QAQpB,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA;;yBAEW,IAAI,CAAC,KAAK;;WAExB;YACH,CAAC,CAAC,IAAI,CAAA,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;iEA2BiD,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;;mDAQhD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;;;;KAI/E,CAAA;IACJ,CAAC;IAEA,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAAQ,CAAC,CAAC,cAAc,EAAE,CAAA;QAEjD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAExC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YAAS,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACvH,OAAO,WAAW,CAAA;QACvB,CAAC,EAAE,EAAE,CAAC,CAAA;QAEH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,EAAQ,QAAQ,EAAE,GAAG,CAAA;;;;OAIvD;YACD,SAAS,EAAE,EAAU,WAAW;aACpC;SACA,CAAC,CAAA;QAEC,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;QAC7D,CAAC;aAAM,CAAC;YAAO,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAA;YACvD,QAAQ,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;QAClC,CAAC;IACD,CAAC;IAEA,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM;QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QACzF,CAAC;IACD,CAAC;;AA5F4B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wDAAiB;AAE7B;IAAd,KAAK,CAAC,MAAM,CAAC;8BAAQ,eAAe;iDAAA;AAE5B;IAAR,KAAK,EAAE;;kDAAe;AApFnB,mBAAmB;IADxB,aAAa,CAAC,sBAAsB,CAAC;GAChC,mBAAmB,CA6KxB","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { navigate, PageView } from '@operato/shell'\n\n@customElement('application-register')\nclass ApplicationRegister extends PageView { static styles = [\n css`\n :host { background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\n\n position: relative;\n\n overflow: auto;\n }\n h2 { margin: var(--title-margin);\n font: var(--title-font);\n color: var(--title-text-color);\n }\n [page-description] { margin: var(--page-description-margin);\n font: var(--page-description-font);\n color: var(--page-description-color);\n }\n [icon] { position: absolute;\n top: 10px;\n right: 10px;\n\n max-width: 80px;\n }\n [icon] img { max-width: 100%;\n max-height: 100%;\n }\n\n img[pagedeco] { width: 150px;\n margin: 0 var(--spacing-large);\n float: left;\n position: relative;\n top: -20px;\n }\n\n [form-container] { background-color: var(--md-sys-color-surface);\n padding: var(--spacing-large);\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n max-width: var(--input-container-max-width);\n position: relative;\n top: -50px;\n clear: both;\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 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 flex: 1;\n }\n select:focus,\n input:focus { outline: none;\n }\n [field-2column] { display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: 15px;\n }\n [field] { display: flex;\n flex-direction: column;\n }\n [grid-span] { grid-column: span 2;\n }\n @media screen and (max-width: 480px) { img[pagedeco] { display: none;\n }\n [form-container] { position: initial;\n }\n [field] { grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: Object }) application: any\n\n @query('form') form!: HTMLFormElement\n\n @state() _icon?: string\n\n get context() { return { title: `application registration`,\n help: 'auth/application'\n }\n }\n\n render() { return html`\n <img src=\"/assets/images/image-api.png\" pagedeco />\n <h2>Register new application</h2>\n <p page-description>\n You can register new application here.<br />\n What type of app are you building? Choose the app type that best suits the audience you’re building for.\n </p>\n\n ${this._icon\n ? html`\n <div icon>\n <img src=${this._icon} />\n </div>\n `\n : html``}\n\n <div form-container>\n <form>\n <div field-2column>\n <div field grid-span>\n <label for=\"name\">name</label>\n <input id=\"name\" type=\"text\" name=\"name\" />\n </div>\n\n <div field grid-span>\n <label for=\"description\">description</label>\n <input id=\"description\" type=\"text\" name=\"description\" />\n </div>\n\n <div field grid-span>\n <label for=\"app-url\">application url</label>\n <input id=\"app-url\" type=\"text\" name=\"url\" />\n </div>\n\n <div field grid-span>\n <label for=\"redirect-url\">redirectUrl</label>\n <input id=\"redirect-url\" type=\"text\" name=\"redirectUrl\" />\n </div>\n\n <div field grid-span>\n <label for=\"icon\">application icon</label>\n <input id=\"icon\" type=\"text\" name=\"icon\" @change=${e => (this._icon = e.target.value)} />\n </div>\n\n <div field grid-span>\n <label for=\"email\">contact email</label>\n <input id=\"email\" type=\"text\" name=\"email\" />\n </div>\n\n <md-elevated-button grid-span @click=${this.createApplication.bind(this)}>register</md-elevated-button>\n </div>\n </form>\n </div>\n `\n }\n\n async createApplication(e) { e.preventDefault()\n\n const formData = new FormData(this.form)\n\n const application = Array.from(formData.entries()).reduce((application, [key, value]) => { application[key] = value\n return application\n }, {})\n\n const response = await client.mutate({ mutation: gql`\n mutation ($application: NewApplication!) { createApplication(application: $application) { id\n }\n }\n `,\n variables: { application\n }\n })\n\n if (response.errors) { console.log('creation fail.')\n } else { const id = response.data.createApplication.id\n navigate(`application/${id}`)\n }\n }\n\n pageUpdated(changes, lifecycle, before) { if (this.active) { this.form.reset()\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../client/pages/application/register.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { FormFieldStyles } from '@operato/styles'\n\nimport { filledValues, missingRequired } from '../../utils/form-values.js'\n\n@customElement('application-register')\nclass ApplicationRegister extends localize(i18next)(PageView) { static styles = [\n FormFieldStyles,\n css`\n :host { background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\n\n position: relative;\n\n overflow: auto;\n }\n h2 { margin: var(--title-margin);\n font: var(--title-font);\n color: var(--title-text-color);\n }\n [page-description] { margin: var(--page-description-margin);\n font: var(--page-description-font);\n color: var(--page-description-color);\n }\n [field-error],\n [form-error] { color: var(--md-sys-color-error);\n font: var(--input-hint-font, inherit);\n margin-top: var(--spacing-tiny, 2px);\n }\n\n input[invalid] { border-color: var(--md-sys-color-error);\n }\n\n [icon] { position: absolute;\n top: 10px;\n right: 10px;\n\n max-width: 80px;\n }\n [icon] img { max-width: 100%;\n max-height: 100%;\n }\n\n img[pagedeco] { width: 150px;\n margin: 0 var(--spacing-large);\n float: left;\n position: relative;\n top: -20px;\n }\n\n [form-container] { background-color: var(--md-sys-color-surface);\n padding: var(--spacing-large);\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n max-width: var(--input-container-max-width);\n position: relative;\n top: -50px;\n clear: both;\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 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 flex: 1;\n }\n select:focus,\n input:focus { outline: none;\n }\n [field-2column] { display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: 15px;\n }\n [field] { display: flex;\n flex-direction: column;\n }\n [grid-span] { grid-column: span 2;\n }\n @media screen and (max-width: 480px) { img[pagedeco] { display: none;\n }\n [form-container] { position: initial;\n }\n [field] { grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: Object }) application: any\n\n @query('form') form!: HTMLFormElement\n\n @state() _icon?: string\n\n /** Required fields the person has not filled in yet. Empty until they press register. */\n @state() missing: string[] = []\n\n /** The mutation was refused. The link already said why; this says the press landed. */\n @state() failed: boolean = false\n\n get context() { return { title: `application registration`,\n help: 'auth/application'\n }\n }\n\n render() { return html`\n <img src=\"/assets/images/image-api.png\" pagedeco />\n <h2>${i18next.t('text.register new application')}</h2>\n <p page-description>${i18next.t('text.an application signs in on its own behalf')}</p>\n\n ${this._icon\n ? html`\n <div icon>\n <img src=${this._icon} />\n </div>\n `\n : html``}\n\n <div form-container>\n <form>\n <div field-2column>\n <div field grid-span>\n <label for=\"name\">${i18next.t('field.name')}</label>\n <input id=\"name\" type=\"text\" name=\"name\" required ?invalid=${this.missing.includes('name')} />\n ${this.missing.includes('name')\n ? html`<div field-error>${i18next.t('text.this field is required')}</div>`\n : ''}\n </div>\n\n <div field grid-span>\n <label for=\"description\">${i18next.t('field.description')}</label>\n <input id=\"description\" type=\"text\" name=\"description\" />\n </div>\n\n <div field grid-span>\n <label for=\"app-url\">${i18next.t('field.application url')}</label>\n <input id=\"app-url\" type=\"url\" name=\"url\" />\n </div>\n\n <div field grid-span>\n <label for=\"redirect-url\">${i18next.t('field.redirect url')}</label>\n <input id=\"redirect-url\" type=\"url\" name=\"redirectUrl\" />\n </div>\n\n <div field grid-span>\n <label for=\"icon\">${i18next.t('field.application icon')}</label>\n <input id=\"icon\" type=\"url\" name=\"icon\" @change=${e => (this._icon = e.target.value)} />\n </div>\n\n <div field grid-span>\n <!-- type=email so the browser checks the shape the server's scalar will check. -->\n <label for=\"email\">${i18next.t('field.contact email')}</label>\n <input id=\"email\" type=\"email\" name=\"email\" />\n </div>\n\n ${this.failed ? html`<div form-error grid-span>${i18next.t('text.could not register')}</div>` : ''}\n\n <button grid-span type=\"submit\" @click=${this.createApplication.bind(this)}>\n ${i18next.t('button.register')}\n </button>\n </div>\n </form>\n </div>\n `\n }\n\n async createApplication(e) { e.preventDefault()\n\n const formData = new FormData(this.form)\n\n /*\n `name` is the only field NewApplication declares non-null. The list comes from the\n schema rather than from taste — requiring more here would invent a rule the product\n does not have, and the person would be stopped by this screen alone.\n */\n const missing = missingRequired(formData, ['name'])\n\n if (missing.length) {\n this.missing = missing\n return\n }\n\n this.missing = []\n\n /*\n This used to send every input, including the untouched ones, each as an empty string.\n `NewApplication.email` is a GraphQLEmailAddress and rejects '' — so registering without\n a contact email failed, on a field the schema marks optional, and the screen printed\n \"creation fail.\" to the console where nobody would see it.\n\n `filledValues` leaves an empty field out instead, which is what optional means on the\n wire.\n */\n const application = filledValues(formData)\n\n const response = await client.mutate({ mutation: gql`\n mutation ($application: NewApplication!) { createApplication(application: $application) { id\n }\n }\n `,\n variables: { application\n }\n })\n\n if (response.errors) {\n /*\n The shared client link already raises a notify event carrying the server's reason, so\n the person sees what went wrong. What was missing is that the screen stayed silent\n and looked idle — the form is still filled in, and nothing said the press had landed.\n */\n this.failed = true\n return\n }\n\n this.failed = false\n navigate(`application/${response.data.createApplication.id}`)\n }\n\n pageUpdated(changes, lifecycle, before) { if (this.active) { this.form.reset()\n }\n }\n}\n"]}
|
|
@@ -13,6 +13,8 @@ export declare class AuthProviderManagementPage extends AuthProviderManagementPa
|
|
|
13
13
|
static styles: import("lit").CSSResult[];
|
|
14
14
|
gristConfig: any;
|
|
15
15
|
mode: 'CARD' | 'GRID' | 'LIST';
|
|
16
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
17
|
+
failed: boolean;
|
|
16
18
|
authProviderTypes: {
|
|
17
19
|
type: string;
|
|
18
20
|
description?: string;
|
|
@@ -47,8 +49,8 @@ export declare class AuthProviderManagementPage extends AuthProviderManagementPa
|
|
|
47
49
|
render(): import("lit-html").TemplateResult<1>;
|
|
48
50
|
pageInitialized(lifecycle: any): Promise<void>;
|
|
49
51
|
fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
|
|
50
|
-
total:
|
|
51
|
-
records: any;
|
|
52
|
+
total: number;
|
|
53
|
+
records: any[];
|
|
52
54
|
}>;
|
|
53
55
|
private deleteAuthProvider;
|
|
54
56
|
private updateAuthProvider;
|