@things-factory/auth-ui 10.1.258 → 10.1.260

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist-client/pages/appliance/home.d.ts +0 -1
  2. package/dist-client/pages/appliance/home.js +5 -6
  3. package/dist-client/pages/appliance/home.js.map +1 -1
  4. package/dist-client/pages/application/applications.d.ts +0 -1
  5. package/dist-client/pages/application/applications.js +5 -6
  6. package/dist-client/pages/application/applications.js.map +1 -1
  7. package/dist-client/pages/audit/audit-page.d.ts +15 -14
  8. package/dist-client/pages/audit/audit-page.js +107 -139
  9. package/dist-client/pages/audit/audit-page.js.map +1 -1
  10. package/dist-client/pages/auth-provider/auth-provider-management.d.ts +9 -4
  11. package/dist-client/pages/auth-provider/auth-provider-management.js +29 -13
  12. package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
  13. package/dist-client/pages/domain-owner/domain-owner-management.js +2 -2
  14. package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
  15. package/dist-client/pages/list-address.test.d.ts +1 -0
  16. package/dist-client/pages/list-address.test.js +51 -0
  17. package/dist-client/pages/list-address.test.js.map +1 -0
  18. package/dist-client/pages/role/role-inheritance-shown.test.js +7 -6
  19. package/dist-client/pages/role/role-inheritance-shown.test.js.map +1 -1
  20. package/dist-client/pages/role/role-list-page.d.ts +20 -27
  21. package/dist-client/pages/role/role-list-page.js +49 -136
  22. package/dist-client/pages/role/role-list-page.js.map +1 -1
  23. package/dist-client/pages/user/user-list-page.d.ts +0 -1
  24. package/dist-client/pages/user/user-list-page.js +5 -6
  25. package/dist-client/pages/user/user-list-page.js.map +1 -1
  26. package/dist-client/tsconfig.tsbuildinfo +1 -1
  27. package/dist-client/utils/audit-words.d.ts +28 -0
  28. package/dist-client/utils/audit-words.js +36 -0
  29. package/dist-client/utils/audit-words.js.map +1 -1
  30. package/dist-client/utils/audit-words.test.js +33 -1
  31. package/dist-client/utils/audit-words.test.js.map +1 -1
  32. package/dist-client/utils/role-scope.d.ts +15 -0
  33. package/dist-client/utils/role-scope.js +13 -0
  34. package/dist-client/utils/role-scope.js.map +1 -0
  35. package/dist-client/utils/role-scope.test.d.ts +1 -0
  36. package/dist-client/utils/role-scope.test.js +18 -0
  37. package/dist-client/utils/role-scope.test.js.map +1 -0
  38. package/package.json +8 -7
  39. package/translations/en.json +1 -1
  40. package/translations/es.json +1 -1
  41. package/translations/ja.json +1 -1
  42. package/translations/ko.json +1 -1
  43. package/translations/ms.json +1 -1
  44. package/translations/zh.json +1 -1
@@ -52,7 +52,6 @@ export declare class Appliances extends Appliances_base {
52
52
  render(): import("lit-html").TemplateResult<1>;
53
53
  pageInitialized(): Promise<void>;
54
54
  private openEditor;
55
- pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
56
55
  fetchHandler({ page, limit, sortings, filters, search }: FetchOption): Promise<{
57
56
  total: number;
58
57
  records: any[];
@@ -83,6 +83,7 @@ let Appliances = class Appliances extends p13n(localize(i18next)(PageView)) {
83
83
  .config=${this.config}
84
84
  .fetchHandler=${this.fetchHandler.bind(this)}
85
85
  .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
86
+ ?url-params-sensitive=${this.active}
86
87
  >
87
88
  </ox-grist>
88
89
 
@@ -179,12 +180,10 @@ let Appliances = class Appliances extends p13n(localize(i18next)(PageView)) {
179
180
  title: applianceId ? i18next.t('text.appliance') : i18next.t('text.register new appliance')
180
181
  });
181
182
  }
182
- async pageUpdated(_changes, _lifecycle) {
183
- if (this.active) {
184
- await this.updateComplete;
185
- this.grist.fetch();
186
- }
187
- }
183
+ /*
184
+ * No read on activation here: the list is bound to the address (FOOTER-BUTTON-REVIEW G09), so it reads its search,
185
+ * sort and page from the address itself whenever the page becomes active.
186
+ */
188
187
  async fetchHandler({ page, limit, sortings = [], filters = [], search }) {
189
188
  const response = await client.query({
190
189
  query: gql `
@@ -1 +1 @@
1
- {"version":3,"file":"home.js","sourceRoot":"","sources":["../../../client/pages/appliance/home.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/appliance-editor.js'\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Registered appliances.\n *\n * ── What this screen looked like before ──────────────────────────────────────\n * A hand-written table with no paging, sorting or search, and three columns that were not\n * data: \"API health\" printed OK, \"Installs\" printed 1, \"status\" printed draft, identical on\n * every row. An appliance that had never checked in read exactly like one reporting fine.\n * Those are gone — Appliance declares none of the three — and the columns here are the\n * fields it does declare.\n *\n * ── ⚠ The access token is not asked for, and a test holds that ──────────────\n * `Appliance.accessToken` carries its own\n * `@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)`. A list\n * is the wrong place for it regardless of who may read it: a token in a grid cell is a token\n * in plain text beside a name, visible to everyone who can open the screen and to anyone\n * looking over their shoulder. The screens that genuinely need it ask one record at a time.\n *\n * `appliance-list-query.test.ts` fails if the word reappears here.\n */\n@customElement('appliance-home')\nexport class Appliances extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.appliances'),\n help: 'auth/appliance',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.register'),\n action: () => this.openEditor(),\n icon: 'add'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openEditor(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n type: 'string',\n name: 'serialNo',\n header: i18next.t('field.serial-no'),\n record: { editable: false },\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'brand',\n header: i18next.t('field.brand'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'model',\n header: i18next.t('field.model'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'netmask',\n header: i18next.t('field.netmask'),\n record: { editable: false },\n width: 160\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n width: 240\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated at'),\n sortable: true,\n width: 180\n }\n ]\n }\n }\n\n /*\n * Registering and editing both happen here, over the list, the way every other resource in\n * this package works. `appliance-register` and `appliance/{id}` were routes; nothing outside\n * auth-ui linked to either.\n */\n private openEditor(applianceId?: string) {\n const popup = openPopup(\n html`<appliance-editor\n .applianceId=${applianceId}\n @appliance-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></appliance-editor>`,\n {\n backdrop: true,\n size: 'medium',\n title: applianceId ? i18next.t('text.appliance') : i18next.t('text.register new appliance')\n }\n )\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n async fetchHandler({ page, limit, sortings = [], filters = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($search: String, $filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n appliances(search: $search, 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: { search, filters, pagination: { page, limit }, sortings },\n context: gqlContext()\n })\n\n const { items, total, failed } = listOutcome(pageFromResponse(response, 'appliances'))\n\n this.failed = failed\n\n return { total, records: items }\n }\n}\n"]}
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../../../client/pages/appliance/home.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/appliance-editor.js'\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Registered appliances.\n *\n * ── What this screen looked like before ──────────────────────────────────────\n * A hand-written table with no paging, sorting or search, and three columns that were not\n * data: \"API health\" printed OK, \"Installs\" printed 1, \"status\" printed draft, identical on\n * every row. An appliance that had never checked in read exactly like one reporting fine.\n * Those are gone — Appliance declares none of the three — and the columns here are the\n * fields it does declare.\n *\n * ── ⚠ The access token is not asked for, and a test holds that ──────────────\n * `Appliance.accessToken` carries its own\n * `@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)`. A list\n * is the wrong place for it regardless of who may read it: a token in a grid cell is a token\n * in plain text beside a name, visible to everyone who can open the screen and to anyone\n * looking over their shoulder. The screens that genuinely need it ask one record at a time.\n *\n * `appliance-list-query.test.ts` fails if the word reappears here.\n */\n@customElement('appliance-home')\nexport class Appliances extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.appliances'),\n help: 'auth/appliance',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.register'),\n action: () => this.openEditor(),\n icon: 'add'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n ?url-params-sensitive=${this.active}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openEditor(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n type: 'string',\n name: 'serialNo',\n header: i18next.t('field.serial-no'),\n record: { editable: false },\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'brand',\n header: i18next.t('field.brand'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'model',\n header: i18next.t('field.model'),\n record: { editable: false },\n sortable: true,\n width: 140\n },\n {\n type: 'string',\n name: 'netmask',\n header: i18next.t('field.netmask'),\n record: { editable: false },\n width: 160\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n width: 240\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated at'),\n sortable: true,\n width: 180\n }\n ]\n }\n }\n\n /*\n * Registering and editing both happen here, over the list, the way every other resource in\n * this package works. `appliance-register` and `appliance/{id}` were routes; nothing outside\n * auth-ui linked to either.\n */\n private openEditor(applianceId?: string) {\n const popup = openPopup(\n html`<appliance-editor\n .applianceId=${applianceId}\n @appliance-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></appliance-editor>`,\n {\n backdrop: true,\n size: 'medium',\n title: applianceId ? i18next.t('text.appliance') : i18next.t('text.register new appliance')\n }\n )\n }\n\n /*\n * No read on activation here: the list is bound to the address (FOOTER-BUTTON-REVIEW G09), so it reads its search,\n * sort and page from the address itself whenever the page becomes active.\n */\n\n async fetchHandler({ page, limit, sortings = [], filters = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($search: String, $filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n appliances(search: $search, 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: { search, 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"]}
@@ -52,7 +52,6 @@ export declare class Applications extends Applications_base {
52
52
  render(): import("lit-html").TemplateResult<1>;
53
53
  pageInitialized(): Promise<void>;
54
54
  private openEditor;
55
- pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
56
55
  fetchHandler({ page, limit, sortings, filters, search }: FetchOption): Promise<{
57
56
  total: number;
58
57
  records: any[];
@@ -84,6 +84,7 @@ let Applications = class Applications extends p13n(localize(i18next)(PageView))
84
84
  .config=${this.config}
85
85
  .fetchHandler=${this.fetchHandler.bind(this)}
86
86
  .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
87
+ ?url-params-sensitive=${this.active}
87
88
  >
88
89
  </ox-grist>
89
90
 
@@ -164,12 +165,10 @@ let Applications = class Applications extends p13n(localize(i18next)(PageView))
164
165
  title: applicationId ? i18next.t('text.application') : i18next.t('text.register new application')
165
166
  });
166
167
  }
167
- async pageUpdated(_changes, _lifecycle) {
168
- if (this.active) {
169
- await this.updateComplete;
170
- this.grist.fetch();
171
- }
172
- }
168
+ /*
169
+ * No read on activation here: the list is bound to the address (FOOTER-BUTTON-REVIEW G09), so it reads its search,
170
+ * sort and page from the address itself whenever the page becomes active.
171
+ */
173
172
  async fetchHandler({ page, limit, sortings = [], filters = [], search }) {
174
173
  const response = await client.query({
175
174
  query: gql `
@@ -1 +1 @@
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'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/application-editor.js'\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 /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\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: () => this.openEditor(),\n icon: 'add'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openEditor(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 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 /*\n * Registering and editing both happen over the list, the way every other resource in this\n * package works. `application-register` and `application/{id}` were routes; nothing outside\n * auth-ui linked to either.\n */\n private openEditor(applicationId?: string) {\n const popup = openPopup(\n html`<application-editor\n .applicationId=${applicationId}\n @application-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></application-editor>`,\n {\n backdrop: true,\n size: 'medium',\n title: applicationId ? i18next.t('text.application') : i18next.t('text.register new application')\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 = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($search: String, $filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n applications(search: $search, 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: { search, 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"]}
1
+ {"version":3,"file":"applications.js","sourceRoot":"","sources":["../../../client/pages/application/applications.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,iCAAiC,CAAA;AAExC,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,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAEpC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAE1E;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAA5D;;QAiBwB,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;IAkKtD,CAAC;aAvLQ,WAAM,GAAG;QACd,qEAAqE;QACrE,qBAAqB;QACrB,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;KAOF;KACF,AAdY,CAcZ;IAWD,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;YACrC,IAAI,EAAE,kBAAkB;YACxB,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,IAAI,CAAC,UAAU,EAAE;oBAC/B,IAAI,EAAE,KAAK;iBACZ;aACF;SACF,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;;gBAIC,IAAI,CAAC,IAAI;kBACP,IAAI,CAAC,MAAM;wBACL,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;kCAClB,IAAI,CAAC,yBAAyB,CAAC,UAAU,CAAE;gCAC7C,IAAI,CAAC,MAAM;;;;QAInC,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,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;4BAC5B,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,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,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;oBAC/B,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC5C,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;oBACjC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC5C,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;;;;OAIG;IACK,UAAU,CAAC,aAAsB;QACvC,MAAM,KAAK,GAAG,SAAS,CACrB,IAAI,CAAA;yBACe,aAAa;+BACP,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;iBACtC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE;6BACP,EACvB;YACE,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC;SAClG,CACF,CAAA;IACH,CAAC;IAED;;;OAGG;IAEH,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,EAAe;QAClF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;OAcT;YACD,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;YACrE,OAAO,EAAE,UAAU,EAAE;SACtB,CAAC,CAAA;QAEF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,gBAAgB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAA;QAExF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB;;;WAGG;QACH,OAAO;YACL,KAAK;YACL,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,CAAC;gBACjC,GAAG,IAAI;gBACP,WAAW,EAAE,sBAAsB,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;aAClF,CAAC,CAAC;SACJ,CAAA;IACH,CAAC;;AAtK4B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;4CAAwB;AACxB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;4CAAY;AACX;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CAAoE;AAGlE;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;4CAAwB;AAEzB;IAA1B,KAAK,CAAC,UAAU,CAAC;;2CAA0B;AAxBjC,YAAY;IADxB,aAAa,CAAC,mBAAmB,CAAC;GACtB,YAAY,CAwLxB","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/application-editor.js'\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 /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\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: () => this.openEditor(),\n icon: 'add'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n ?url-params-sensitive=${this.active}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openEditor(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 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 /*\n * Registering and editing both happen over the list, the way every other resource in this\n * package works. `application-register` and `application/{id}` were routes; nothing outside\n * auth-ui linked to either.\n */\n private openEditor(applicationId?: string) {\n const popup = openPopup(\n html`<application-editor\n .applicationId=${applicationId}\n @application-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></application-editor>`,\n {\n backdrop: true,\n size: 'medium',\n title: applicationId ? i18next.t('text.application') : i18next.t('text.register new application')\n }\n )\n }\n\n /*\n * No read on activation here: the list is bound to the address (FOOTER-BUTTON-REVIEW G09), so it reads its search,\n * sort and page from the address itself whenever the page becomes active.\n */\n\n async fetchHandler({ page, limit, sortings = [], filters = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($search: String, $filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n applications(search: $search, 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: { search, 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"]}
@@ -14,16 +14,8 @@ export declare class AuditPage extends AuditPageBase {
14
14
  static styles: CSSResultGroup;
15
15
  mode: 'CARD' | 'GRID' | 'LIST';
16
16
  private view;
17
- private days;
18
- private from;
19
- private to;
20
- private actor?;
21
- private door?;
22
- private outcome;
23
- /** How many records each outcome chip would show under the other conditions, counted by the records' own door. */
24
- private outcomes;
17
+ private asked;
25
18
  private auditHealth;
26
- private get outcomeCounts();
27
19
  private get health();
28
20
  private grist;
29
21
  get context(): {
@@ -36,13 +28,22 @@ export declare class AuditPage extends AuditPageBase {
36
28
  render(): import("lit-html").TemplateResult<1>;
37
29
  private renderControls;
38
30
  private renderHealth;
39
- private show;
40
- private lastDays;
41
- private pick;
31
+ /**
32
+ * Narrow the records to a person, a privilege or an outcome - from a number on the people or privileges view, or the
33
+ * 「모든 사람」·「모든 권한」 buttons. It shows the records and sets those of the grid's filters it names, keeping the
34
+ * others (the period among them); the grid writes them to the address and reads again.
35
+ */
42
36
  private narrow;
43
- pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
37
+ private filterColumns;
38
+ countHandler({ filters }: FetchOption): Promise<{
39
+ outcome?: undefined;
40
+ } | {
41
+ outcome: {
42
+ [k: string]: any;
43
+ };
44
+ }>;
44
45
  private configOf;
45
- fetchHandler({ page, limit }: FetchOption): Promise<{
46
+ fetchHandler({ page, limit, filters }: FetchOption): Promise<{
46
47
  total: any;
47
48
  records: any;
48
49
  }>;
@@ -14,42 +14,23 @@ import { PageView } from '@operato/shell';
14
14
  import { ButtonStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
15
15
  import { isMobileDevice } from '@operato/utils';
16
16
  import { statusRenderer } from '../../components/user-status-renderer.js';
17
- import { doorWords, lastDays, recordWords } from '../../utils/audit-words.js';
17
+ import { AUDIT_DEFAULT_DAYS, auditQueryOf, doorWords, recordWords } from '../../utils/audit-words.js';
18
18
  const PERIODS = [7, 30, 90];
19
19
  const VIEWS = ['people', 'doors', 'records'];
20
20
  const OUTCOMES = ['', 'denied', 'error', 'failed'];
21
21
  const AuditPageBase = p13n(localize(i18next)(PageView));
22
- function dateInputValue(date) {
23
- const pad = (n) => String(n).padStart(2, '0');
24
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
25
- }
26
22
  let AuditPage = class AuditPage extends AuditPageBase {
27
23
  constructor() {
28
24
  super(...arguments);
29
25
  this.mode = isMobileDevice() ? 'LIST' : 'GRID';
30
- this.view = 'people';
31
- this.days = 30;
32
- this.from = lastDays(30).from;
33
- this.to = new Date();
34
- this.outcome = '';
35
- /** How many records each outcome chip would show under the other conditions, counted by the records' own door. */
36
26
  /*
37
- * The outcome chips' numbers - the records' own door asked for its total once per outcome, under the period, the
38
- * person and the privilege narrowed to, in one request. The chips keep their numbers while new ones are read.
27
+ * The view is page state: it switches the whole list - its columns and its query - not a condition of one list.
28
+ * The conditions - the period, the person and the privilege narrowed to, the outcome - are the grid's own filters,
29
+ * kept in the address (FOOTER-BUTTON-REVIEW G09), and every view declares the same ones, so a period chosen on one
30
+ * view holds on the others. `asked` is what the grid last read with; the narrowed person and privilege are said from it.
39
31
  */
40
- this.outcomes = new QueryController(this, gql `
41
- query (${OUTCOMES.map((_, at) => `$f${at}: AuditRecordFilter!`).join(', ')}) {
42
- ${OUTCOMES.map((_, at) => `c${at}: auditRecords(filter: $f${at}) { total }`).join('\n')}
43
- }
44
- `, {
45
- variables: () => {
46
- if (this.view !== 'records')
47
- return undefined;
48
- const filter = { from: this.from.toISOString(), to: this.to.toISOString(), actorId: this.actor?.id, door: this.door };
49
- return Object.fromEntries(OUTCOMES.map((outcome, at) => [`f${at}`, { ...filter, outcome: outcome || undefined, page: 1, limit: 1 }]));
50
- },
51
- keepPrevious: true
52
- });
32
+ this.view = 'people';
33
+ this.asked = [];
53
34
  this.auditHealth = new QueryController(this, gql `{ auditHealth { failures since } }`);
54
35
  }
55
36
  static { this.styles = [
@@ -63,24 +44,6 @@ let AuditPage = class AuditPage extends AuditPageBase {
63
44
  flex-wrap: wrap;
64
45
  }
65
46
 
66
- [group] {
67
- display: flex;
68
- align-items: center;
69
- gap: var(--spacing-tiny, 2px);
70
- }
71
-
72
- input[type='date'] {
73
- height: var(--context-action-height, 28px);
74
- box-sizing: border-box;
75
- border: 1px solid var(--md-sys-color-outline-variant);
76
- border-radius: var(--md-sys-shape-corner-small, 4px);
77
- background: var(--md-sys-color-surface-container-lowest);
78
- color: var(--md-sys-color-on-surface);
79
- font: inherit;
80
- font-size: var(--ui-t-body, 13px);
81
- padding: 0 var(--spacing-small, 4px);
82
- }
83
-
84
47
  [narrowed] {
85
48
  display: flex;
86
49
  align-items: center;
@@ -96,10 +59,6 @@ let AuditPage = class AuditPage extends AuditPageBase {
96
59
  }
97
60
  `
98
61
  ]; }
99
- get outcomeCounts() {
100
- const data = this.outcomes.data;
101
- return new Map(OUTCOMES.map((outcome, at) => [outcome || 'all', data?.[`c${at}`]?.total ?? 0]));
102
- }
103
62
  get health() {
104
63
  if (this.auditHealth.data?.auditHealth)
105
64
  return this.auditHealth.data.auditHealth;
@@ -115,7 +74,14 @@ let AuditPage = class AuditPage extends AuditPageBase {
115
74
  };
116
75
  }
117
76
  render() {
118
- return html `${keyed(this.view, html `<ox-grist class="page-fill" .mode=${this.mode} .config=${this.configOf(this.view)} .fetchHandler=${this.fetchHandler.bind(this)}>
77
+ return html `${keyed(this.view, html `<ox-grist
78
+ class="page-fill"
79
+ .mode=${this.mode}
80
+ .config=${this.configOf(this.view)}
81
+ .fetchHandler=${this.fetchHandler.bind(this)}
82
+ .countHandler=${this.countHandler.bind(this)}
83
+ ?url-params-sensitive=${this.active}
84
+ >
119
85
  <div slot="headroom" class="header">
120
86
  <div class="filters">${this.renderControls()}</div>
121
87
  </div>
@@ -123,47 +89,27 @@ let AuditPage = class AuditPage extends AuditPageBase {
123
89
  }
124
90
  renderControls() {
125
91
  const t = i18next.t.bind(i18next);
126
- /* One of each is always chosen: a press on the chosen chip leaves it chosen. */
127
- const choose = (e, chosen, pick) => {
128
- if (e.detail)
129
- pick(e.detail);
130
- else
131
- e.target.value = chosen;
132
- };
92
+ const { actor, door } = auditQueryOf(this.asked);
133
93
  return html `
134
94
  <ox-input-select-buttons
135
95
  .options=${VIEWS.map(view => ({ value: view, display: t(`audit.view.${view}`) }))}
136
96
  .value=${this.view}
137
- @change=${(e) => choose(e, this.view, view => this.show(view))}
97
+ @change=${(e) => {
98
+ /* One view is always shown: a press on the chosen chip leaves it chosen. */
99
+ if (e.detail)
100
+ this.view = e.detail;
101
+ else
102
+ e.target.value = this.view;
103
+ }}
138
104
  ></ox-input-select-buttons>
139
105
 
140
- <div group>
141
- <ox-input-select-buttons
142
- .options=${PERIODS.map(days => ({ value: String(days), display: t('audit.period.days', { count: days }) }))}
143
- .value=${this.days ? String(this.days) : undefined}
144
- @change=${(e) => choose(e, this.days ? String(this.days) : undefined, days => this.lastDays(Number(days)))}
145
- ></ox-input-select-buttons>
146
- <input type="date" aria-label=${t('audit.period.from')} .value=${dateInputValue(this.from)} @change=${(e) => this.pick('from', e)} />
147
- <span>~</span>
148
- <input type="date" aria-label=${t('audit.period.to')} .value=${dateInputValue(this.to)} @change=${(e) => this.pick('to', e)} />
149
- </div>
106
+ <ox-filters-form without-search></ox-filters-form>
150
107
 
151
- ${this.view === 'records'
152
- ? html `<ox-input-select-buttons
153
- .options=${OUTCOMES.map(outcome => ({
154
- value: outcome || 'all',
155
- display: t(outcome ? `audit.outcome.${outcome}` : 'audit.outcome.all'),
156
- count: this.outcomeCounts.get(outcome || 'all') ?? 0
157
- }))}
158
- .value=${this.outcome || 'all'}
159
- @change=${(e) => choose(e, this.outcome || 'all', outcome => this.narrow({ outcome: (outcome === 'all' ? '' : outcome) }))}
160
- ></ox-input-select-buttons>`
161
- : nothing}
162
- ${this.view === 'records' && this.actor
163
- ? html `<span narrowed>${this.actor.name}<button quiet small @click=${() => this.narrow({ actor: undefined })}>${t('audit.button.all-people')}</button></span>`
108
+ ${this.view === 'records' && actor
109
+ ? html `<span narrowed>${actor.name}<button quiet small @click=${() => this.narrow({ actor: undefined })}>${t('audit.button.all-people')}</button></span>`
164
110
  : nothing}
165
- ${this.view === 'records' && this.door
166
- ? html `<span narrowed>${doorWords(t, this.door)}<button quiet small @click=${() => this.narrow({ door: undefined })}>${t('audit.button.all-privileges')}</button></span>`
111
+ ${this.view === 'records' && door
112
+ ? html `<span narrowed>${doorWords(t, door)}<button quiet small @click=${() => this.narrow({ door: undefined })}>${t('audit.button.all-privileges')}</button></span>`
167
113
  : nothing}
168
114
  ${this.renderHealth()}
169
115
  `;
@@ -177,37 +123,74 @@ let AuditPage = class AuditPage extends AuditPageBase {
177
123
  return nothing;
178
124
  return html `<span health>${i18next.t('audit.health.failures', { count: health.failures, since: formatWhen(health.since, { lang: i18next.language, style: 'datetime' }) ?? '' })}</span>`;
179
125
  }
180
- show(view) {
181
- this.view = view;
182
- }
183
- lastDays(days) {
184
- const { from, to } = lastDays(days);
185
- this.days = days;
186
- this.from = from;
187
- this.to = to;
188
- this.grist?.fetch();
126
+ /**
127
+ * Narrow the records to a person, a privilege or an outcome - from a number on the people or privileges view, or the
128
+ * 「모든 사람」·「모든 권한」 buttons. It shows the records and sets those of the grid's filters it names, keeping the
129
+ * others (the period among them); the grid writes them to the address and reads again.
130
+ */
131
+ async narrow(change) {
132
+ if (this.view !== 'records') {
133
+ this.view = 'records';
134
+ await this.updateComplete;
135
+ }
136
+ const values = { ...change, ...('outcome' in change ? { outcome: change.outcome || 'all' } : {}) };
137
+ const names = Object.keys(values);
138
+ const filters = [
139
+ ...(this.grist?.filters || []).filter((one) => !names.includes(one.name)),
140
+ ...names.filter(name => values[name] !== undefined).map(name => ({ name, operator: 'eq', value: values[name] }))
141
+ ];
142
+ this.grist?.dispatchEvent(new CustomEvent('fetch-params-change', { bubbles: true, composed: true, detail: { filters, from: 'audit-narrow' } }));
189
143
  }
190
- pick(which, event) {
191
- const value = event.target.value;
192
- if (!value)
193
- return;
194
- const [year, month, day] = value.split('-').map(Number);
195
- this[which] = which === 'from' ? new Date(year, month - 1, day, 0, 0, 0) : new Date(year, month - 1, day, 23, 59, 59, 999);
196
- this.days = null;
197
- this.grist?.fetch();
144
+ /*
145
+ * The conditions every view declares - hidden columns, since no column shows them. The day window and the dates are
146
+ * drawn on every view; the outcome only on the records; the person and the privilege are said beside the filters
147
+ * (`narrowed`) and set from the numbers, never typed.
148
+ */
149
+ filterColumns(view) {
150
+ const t = i18next.t.bind(i18next);
151
+ const hiddenColumn = (name, header, filter) => ({ type: 'string', name, header, hidden: true, record: { editable: false }, filter });
152
+ return [
153
+ hiddenColumn('days', t('audit.period.range'), {
154
+ type: 'select-buttons',
155
+ operator: 'eq',
156
+ label: '',
157
+ value: String(AUDIT_DEFAULT_DAYS),
158
+ options: PERIODS.map(days => ({ value: String(days), display: t('audit.period.days', { count: days }) }))
159
+ }),
160
+ hiddenColumn('period', t('audit.period.range'), { type: 'date', operator: 'between' }),
161
+ hiddenColumn('outcome', t('audit.view.records'), {
162
+ type: 'select-buttons',
163
+ operator: 'eq',
164
+ label: '',
165
+ value: 'all',
166
+ hidden: view !== 'records',
167
+ options: OUTCOMES.map(outcome => ({ value: outcome || 'all', display: t(outcome ? `audit.outcome.${outcome}` : 'audit.outcome.all') }))
168
+ }),
169
+ hiddenColumn('actor', t('audit.field.actor'), { type: 'string', operator: 'eq', hidden: true }),
170
+ hiddenColumn('door', t('audit.field.privilege'), { type: 'string', operator: 'eq', hidden: true })
171
+ ];
198
172
  }
199
- narrow(change) {
200
- Object.assign(this, change);
173
+ /*
174
+ * The outcome chips' numbers on the records view - the records' own door asked for its total once per outcome, under
175
+ * the period, the person and the privilege narrowed to, in one request.
176
+ */
177
+ async countHandler({ filters = [] }) {
201
178
  if (this.view !== 'records')
202
- this.view = 'records';
203
- else
204
- this.grist?.fetch();
205
- }
206
- async pageUpdated(_changes, _lifecycle) {
207
- if (this.active) {
208
- await this.updateComplete;
209
- this.grist?.fetch();
179
+ return {};
180
+ const { from, to, actor, door } = auditQueryOf(filters);
181
+ const filter = { from: from.toISOString(), to: to.toISOString(), actorId: actor?.id, door };
182
+ const response = await client.query({
183
+ query: gql `
184
+ query (${OUTCOMES.map((_, at) => `$f${at}: AuditRecordFilter!`).join(', ')}) {
185
+ ${OUTCOMES.map((_, at) => `c${at}: auditRecords(filter: $f${at}) { total }`).join('\n')}
210
186
  }
187
+ `,
188
+ variables: Object.fromEntries(OUTCOMES.map((outcome, at) => [`f${at}`, { ...filter, outcome: outcome || undefined, page: 1, limit: 1 }])),
189
+ fetchPolicy: 'no-cache'
190
+ });
191
+ if (response.errors?.length)
192
+ throw response.errors[0];
193
+ return { outcome: Object.fromEntries(OUTCOMES.map((outcome, at) => [outcome || 'all', response.data?.[`c${at}`]?.total ?? 0])) };
211
194
  }
212
195
  configOf(view) {
213
196
  const t = i18next.t.bind(i18next);
@@ -241,7 +224,8 @@ let AuditPage = class AuditPage extends AuditPageBase {
241
224
  number('checkins', t('audit.field.checkins')),
242
225
  number('failedSignins', t('audit.field.failed-signins'), record => this.narrow({ actor: person(record), door: undefined, outcome: 'failed' })),
243
226
  number('granted', t('audit.field.granted'), record => this.narrow({ actor: person(record), door: undefined, outcome: '' })),
244
- number('denied', t('audit.field.denied'), record => this.narrow({ actor: person(record), door: undefined, outcome: 'denied' }))
227
+ number('denied', t('audit.field.denied'), record => this.narrow({ actor: person(record), door: undefined, outcome: 'denied' })),
228
+ ...this.filterColumns(view)
245
229
  ]
246
230
  };
247
231
  }
@@ -261,7 +245,8 @@ let AuditPage = class AuditPage extends AuditPageBase {
261
245
  },
262
246
  number('people', t('audit.field.people')),
263
247
  number('granted', t('audit.field.granted'), record => this.narrow({ door: record.door, actor: undefined, outcome: '' })),
264
- number('denied', t('audit.field.denied'), record => this.narrow({ door: record.door, actor: undefined, outcome: 'denied' }))
248
+ number('denied', t('audit.field.denied'), record => this.narrow({ door: record.door, actor: undefined, outcome: 'denied' })),
249
+ ...this.filterColumns(view)
265
250
  ]
266
251
  };
267
252
  }
@@ -275,13 +260,16 @@ let AuditPage = class AuditPage extends AuditPageBase {
275
260
  { type: 'string', name: 'operation', header: t('audit.field.request'), record: { editable: false }, width: 200 },
276
261
  { type: 'string', name: 'what', header: t('audit.field.actions'), record: { editable: false }, width: 360 },
277
262
  { type: 'string', name: 'ip', header: t('audit.field.address'), record: { editable: false }, width: 120 },
278
- { type: 'integer', name: 'durationMs', header: t('audit.field.duration'), record: { editable: false, align: 'right' }, width: 80 }
263
+ { type: 'integer', name: 'durationMs', header: t('audit.field.duration'), record: { editable: false, align: 'right' }, width: 80 },
264
+ ...this.filterColumns(view)
279
265
  ]
280
266
  };
281
267
  }
282
- async fetchHandler({ page, limit }) {
268
+ async fetchHandler({ page, limit, filters = [] }) {
283
269
  const t = i18next.t.bind(i18next);
284
- const period = { from: this.from.toISOString(), to: this.to.toISOString() };
270
+ this.asked = filters;
271
+ const { from, to, actor, door, outcome } = auditQueryOf(filters);
272
+ const period = { from: from.toISOString(), to: to.toISOString() };
285
273
  if (this.view === 'people') {
286
274
  const response = await client.query({
287
275
  query: gql `
@@ -379,9 +367,9 @@ let AuditPage = class AuditPage extends AuditPageBase {
379
367
  variables: {
380
368
  filter: {
381
369
  ...period,
382
- actorId: this.actor?.id,
383
- door: this.door,
384
- outcome: this.outcome || undefined,
370
+ actorId: actor?.id,
371
+ door,
372
+ outcome: outcome || undefined,
385
373
  page,
386
374
  limit
387
375
  }
@@ -413,28 +401,8 @@ __decorate([
413
401
  ], AuditPage.prototype, "view", void 0);
414
402
  __decorate([
415
403
  state(),
416
- __metadata("design:type", Object)
417
- ], AuditPage.prototype, "days", void 0);
418
- __decorate([
419
- state(),
420
- __metadata("design:type", Date)
421
- ], AuditPage.prototype, "from", void 0);
422
- __decorate([
423
- state(),
424
- __metadata("design:type", Date)
425
- ], AuditPage.prototype, "to", void 0);
426
- __decorate([
427
- state(),
428
- __metadata("design:type", Object)
429
- ], AuditPage.prototype, "actor", void 0);
430
- __decorate([
431
- state(),
432
- __metadata("design:type", String)
433
- ], AuditPage.prototype, "door", void 0);
434
- __decorate([
435
- state(),
436
- __metadata("design:type", String)
437
- ], AuditPage.prototype, "outcome", void 0);
404
+ __metadata("design:type", Array)
405
+ ], AuditPage.prototype, "asked", void 0);
438
406
  __decorate([
439
407
  query('ox-grist'),
440
408
  __metadata("design:type", Function)