@things-factory/auth-ui 10.1.26 → 10.1.28
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/abstract-auth-page.js +2 -0
- package/dist-client/components/abstract-auth-page.js.map +1 -1
- package/dist-client/components/abstract-password-reset.js +2 -0
- package/dist-client/components/abstract-password-reset.js.map +1 -1
- package/dist-client/entries/auth/activate.js +2 -0
- package/dist-client/entries/auth/activate.js.map +1 -1
- package/dist-client/entries/auth/checkin.js +2 -0
- package/dist-client/entries/auth/checkin.js.map +1 -1
- package/dist-client/entries/auth/result.js +2 -0
- package/dist-client/entries/auth/result.js.map +1 -1
- package/dist-client/entries/oauth2/oauth2-decision-error-page.js +2 -0
- package/dist-client/entries/oauth2/oauth2-decision-error-page.js.map +1 -1
- package/dist-client/entries/oauth2/oauth2-decision-page.js +2 -0
- package/dist-client/entries/oauth2/oauth2-decision-page.js.map +1 -1
- package/dist-client/entries/public/home.js +2 -0
- package/dist-client/entries/public/home.js.map +1 -1
- package/dist-client/entry-control-size-styles.d.ts +15 -0
- package/dist-client/entry-control-size-styles.js +37 -0
- package/dist-client/entry-control-size-styles.js.map +1 -0
- package/dist-client/pages/app-binding/app-bindings.d.ts +0 -2
- package/dist-client/pages/app-binding/app-bindings.js +5 -17
- package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
- package/dist-client/pages/appliance/home.d.ts +0 -2
- package/dist-client/pages/appliance/home.js +5 -17
- package/dist-client/pages/appliance/home.js.map +1 -1
- package/dist-client/pages/application/applications.d.ts +0 -2
- package/dist-client/pages/application/applications.js +5 -17
- package/dist-client/pages/application/applications.js.map +1 -1
- package/dist-client/pages/attribute/attribute-set-item-list.js +2 -1
- package/dist-client/pages/attribute/attribute-set-item-list.js.map +1 -1
- package/dist-client/pages/attribute/attribute-set-management.d.ts +0 -5
- package/dist-client/pages/attribute/attribute-set-management.js +9 -24
- package/dist-client/pages/attribute/attribute-set-management.js.map +1 -1
- package/dist-client/pages/auth-provider/auth-provider-management.d.ts +0 -2
- package/dist-client/pages/auth-provider/auth-provider-management.js +7 -17
- package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
- package/dist-client/pages/domain/domain-management.d.ts +0 -2
- package/dist-client/pages/domain/domain-management.js +6 -19
- package/dist-client/pages/domain/domain-management.js.map +1 -1
- package/dist-client/pages/domain-link/domain-link-management.js +6 -11
- package/dist-client/pages/domain-link/domain-link-management.js.map +1 -1
- package/dist-client/pages/domain-owner/domain-owner-management.d.ts +0 -2
- package/dist-client/pages/domain-owner/domain-owner-management.js +5 -17
- package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
- package/dist-client/pages/env-var/env-var-list-page.d.ts +0 -2
- package/dist-client/pages/env-var/env-var-list-page.js +6 -19
- package/dist-client/pages/env-var/env-var-list-page.js.map +1 -1
- package/dist-client/pages/partner/partner-management.d.ts +1 -0
- package/dist-client/pages/partner/partner-management.js +43 -18
- package/dist-client/pages/partner/partner-management.js.map +1 -1
- package/dist-client/pages/profile.js +7 -12
- package/dist-client/pages/profile.js.map +1 -1
- package/dist-client/pages/role/role-list-page.js +4 -10
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/pages/role/role-privilege-page.d.ts +4 -1
- package/dist-client/pages/role/role-privilege-page.js +47 -26
- package/dist-client/pages/role/role-privilege-page.js.map +1 -1
- package/dist-client/pages/user/user-list-page.d.ts +22 -1
- package/dist-client/pages/user/user-list-page.js +81 -67
- package/dist-client/pages/user/user-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/tests/entry-control-sizes.test.ts +91 -0
- package/tests/page-state.test.ts +49 -0
- package/translations/en.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-list-page.js","sourceRoot":"","sources":["../../../client/pages/user/user-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/oops/ox-oops-note.js'\nimport '../../components/user-detail.js'\nimport '../../components/user-invite.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 사용자 목록 — **서버가 거르고, 서버가 쪽을 나눈다.**\n *\n * ── 옛 화면이 하던 것 (`pages/user/user-management.ts`) ──────────────────────\n * `query { users { items { … } } }` 로 **도메인의 모든 사용자**를 한 번에 가져왔다. 한도도\n * 서버 검색도 없었고, 프레임워크가 이미 그것을 경고하고 있었다\n * (`unbounded list query on \"User\"`). 사용자는 권한(84행)과 달리 실제로 늘어난다.\n *\n * 그런데 **서버는 처음부터 받을 준비가 되어 있었다** — `users(filters:, pagination:, sortings:)`\n * 가 `ListParam` 을 받고 `name`·`email`·`description` 을 검색어로 본다\n * (`user-query.ts`). 화면이 안 쓰고 있었을 뿐이다.\n *\n * ── 왜 `ox-grist` 인가 ──────────────────────────────────────────────────────\n * 손으로 만든 목록이 잃는 것을 그것이 이미 갖고 있다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 사람마다 기억하는 개인 설정, 카드↔표 모드, 내보내기. `figure-list-page` 가 같은 이유로\n * 옮겼고 그 판단을 그대로 따른다.\n *\n * ── 옛 화면의 탭을 없앤 이유 ────────────────────────────────────────────────\n * 탭이 `userType` 값으로 갈렸다. 그런데 그 칸은 `varchar` 이고 코드가 예상하는 넷\n * (`admin`·`user`·`application`·`appliance`) 밖의 값이거나 비어 있으면 **그 사람이 어느 탭에도\n * 안 나온다 — 화면에서 조용히 사라진다.**\n *\n * 여기서는 사람만 싣는다. 애플리케이션과 어플라이언스는 자기 화면(`applications`·`appliance`)에\n * 있고, 거기서 자기 자격증명까지 함께 다루는 것이 맞다. 다만 **거르는 것은 서버가 해야 한다** —\n * 지금은 `users` 가 종류를 안 가리므로 이 화면이 filter 로 요구한다.\n */\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정(모드·정렬·쪽 크기를 사람마다\n * 기억하는 것)을 붙일 수 없다. `figure-list-page` 가 같은 주석을 달고 있다.\n */\nconst UserListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('user-list-page')\nexport class UserListPage extends UserListPageBase {\n static styles: CSSResultGroup = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n /* 떠 있는 단추의 기준 상자 */\n position: relative;\n }\n\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /*\n ── 상태 배지 ──────────────────────────────────────────────────────────\n 색은 **짝으로** 쓴다(container / on-container). 한쪽만 쓰면 야간에 대비가 깨진다.\n 그리고 색은 「무엇이 나를 기다리나」에만 쓴다 — 활성은 기본값이라 색이 없다.\n 담는 면이 행이므로 모서리는 작게 잡는다.\n */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n .badge[quiet] {\n color: var(--md-sys-color-outline);\n }\n\n /* 격자를 떼지 않고 위에 겹친다 — 격자가 살아 있어야 다시 읽을 수 있다. */\n ox-oops-note[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n background-color: var(--md-sys-color-surface);\n }\n\n /*\n ── 사람 부르기 ────────────────────────────────────────────────────────\n 이 화면의 주된 동작 하나. 글자를 함께 둔다 — 더하기 표만으로는 무엇을 부르는지 모른다.\n 떠 있는 것의 모서리는 알약이다(담는 면을 따르지 않는다).\n */\n button[fab] {\n position: absolute;\n right: var(--spacing-large, 12px);\n bottom: var(--spacing-large, 12px);\n z-index: 10;\n\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 10px 18px 10px 14px;\n font: var(--label-font, inherit);\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: var(--md-sys-shape-corner-full, 999px);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);\n cursor: pointer;\n }\n button[fab]:hover {\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n }\n button[fab] md-icon {\n --md-icon-size: 20px;\n }\n `\n ]\n\n @property({ type: Object }) config: any\n /*\n * 카드가 아니라 **행**이다. 카드는 그림으로 알아보는 물건(형상 카탈로그)의 모양이고,\n * 사람은 이름으로 알아본다. 좁은 화면에서는 `LIST` 가 같은 행을 접어 그린다.\n */\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n @state() private lockDurationMinutes: number = 10\n\n /*\n * **읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다.** 셋을 한 문장으로 그리면 못 읽은 것을\n * 「없다」로 읽고, 사용자가 이미 있는 것을 만들러 간다.\n */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.user management'),\n help: 'auth/users'\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 <div class=\"filters\">\n <ox-filters-form autofocus></ox-filters-form>\n </div>\n <ox-context-page-toolbar class=\"actions\" .context=${this.context}></ox-context-page-toolbar>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n\n <button fab @click=${() => this.onInvite()}>\n <md-icon>person_add</md-icon>${i18next.t('label.add user')}\n </button>\n `\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * 거른 결과가 비어 있는 것은 **가르칠 자리가 아니다.** 그때는 격자가 스스로 「없음」을\n * 말하게 두고, 여기서는 아무것도 그리지 않는다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n return html`<ox-oops-note\n note\n icon=\"error_outline\"\n title=${i18next.t('text.failed to fetch')}\n description=${i18next.t('text.please try again')}\n ></ox-oops-note>`\n }\n\n if (this.total === 0 && !this.filtered) {\n /* 새 도메인의 첫 화면. 소유자는 역할 없이도 모두 열리므로 그 사실부터 말한다. */\n return html`<ox-oops-note\n note\n icon=\"group_add\"\n title=${i18next.t('text.no other user yet')}\n description=${i18next.t('text.owner opens everything without a role')}\n ></ox-oops-note>`\n }\n\n return nothing\n }\n\n async pageInitialized() {\n this.config = {\n /*\n * 카드에 실을 것. 표 모드도 같은 컬럼 설정을 쓰므로 **두 모드가 다른 말을 하지 않는다.**\n */\n list: {\n fields: ['name', 'email'],\n details: ['status', 'roles']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { align: 'left', renderer: nameWithOwnerMark },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { align: 'left' },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * 상태 하나로 두 가지를 말한다 — 지금 상태와, 잠긴 경우 **언제 풀리는지**.\n * `lockedAt` 과 설정된 잠금 시간으로 계산한다. 옛 화면은 「잠김」까지만 말했고,\n * 그래서 관리자가 기다리면 되는지 풀어 줘야 하는지 알 수 없었다.\n */\n type: 'string',\n name: 'status',\n header: i18next.t('field.status'),\n record: { align: 'center', renderer: statusRenderer(() => this.lockDurationMinutes) },\n filter: {\n options: ['', 'activated', 'locked', 'inactive'],\n multiple: false,\n operator: 'eq'\n },\n sortable: true,\n width: 140\n },\n {\n /*\n * 역할이 비어 있는 것을 **눈에 띄게** 그린다. 역할 없는 활성 계정은 로그인은 되는데\n * 화면이 하나도 없다 — 옛 화면에서는 그것이 빈 칸이라 아무도 알아채지 못했다.\n */\n type: 'string',\n name: 'roles',\n header: i18next.t('field.roles'),\n record: { align: 'left', renderer: rolesRenderer },\n width: 260\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n selectable: { multiple: false },\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => this.onSelectUser(record)\n }\n },\n sorters: [{ name: 'name' }]\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 /**\n * 사람만 가져온다. **거르는 것은 서버가 한다** — 옛 화면은 전부 받아 클라이언트에서 나눴다.\n *\n * `userType` 은 `varchar` 라 값이 넷 밖으로 나갈 수 있다. 그래서 「기계가 아닌 것」을\n * `not-in` 으로 요구한다 — 「사람인 것」을 `in` 으로 요구하면 값이 비어 있는 사람이 사라진다.\n * (`userType` 을 enum 으로 못 박는 것은 §3 의 일이다.)\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n /* 사람만 보려고 이 화면이 얹는 조건은 「거른 것」으로 세지 않는다 — 그것까지 세면\n 아무도 없는 도메인이 「거른 결과가 없다」로 보여 첫 안내가 안 나온다. */\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: users(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n username\n name\n email\n userType\n status\n lockedAt\n owner\n roles {\n id\n name\n }\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n ...filters,\n {\n name: 'userType',\n operator: 'notin',\n value: ['application', 'appliance']\n }\n ],\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }]\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n throw e\n }\n }\n\n /**\n * 고른 사람의 역할을 고치는 자리를 연다.\n *\n * `domain-management` 가 격자 행에서 `ox-attribute-view` 를 여는 것과 같은 모양이다 —\n * `handlers.click` 에서 `openPopup` 에 template 을 넘긴다.\n *\n * `escapable` 은 넘기지 않는다. 이 앱의 기본값이 `false` 이고, 그것이 곧 **ESC 에 먼저\n * 묻는다**는 뜻이다 — 편집면에서 원하는 거동이 기본값이다.\n */\n private onSelectUser(record: any) {\n const popup = openPopup(\n html`\n <user-detail\n .user=${record}\n @close=${() => popup.close()}\n @user-updated=${() => {\n popup.close()\n /* 고친 사람만 다시 읽을 길이 없다 — 격자가 가진 것은 쪽 단위 새로고침뿐이다. */\n this.grist.fetch()\n }}\n ></user-detail>\n `,\n {\n backdrop: true,\n size: 'large',\n title: record?.name || record?.username || record?.email\n }\n )\n }\n\n /** 목록의 역할 편집과 같은 모양으로 연다 — `openPopup` 에 template 을 넘긴다. */\n private onInvite() {\n const popup = openPopup(\n html`\n <user-invite\n @close=${() => popup.close()}\n @user-invited=${() => {\n popup.close()\n this.grist.fetch()\n }}\n ></user-invite>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('label.add user')\n }\n )\n }\n}\n\n/** 소유자는 이름 옆에 표시한다. 서버가 사람마다 `owner` 를 채워 준다(공동 소유자도 함께). */\nfunction nameWithOwnerMark(value: string, _column: any, record: any) {\n return record?.owner\n ? html`<md-icon style=\"--md-icon-size: 16px; vertical-align: -3px;\">supervisor_account</md-icon> ${value}`\n : html`${value}`\n}\n\n/**\n * 잠긴 계정이 **언제 풀리는지**까지 말한다.\n *\n * 판정은 서버(`account-lock-rule.ts`)가 하고 여기서는 남은 시간만 보여 준다 —\n * 화면이 판정을 다시 하면 두 곳이 다른 답을 낸다. `lockedAt` 이 비어 있으면 자동 해제가\n * 없다는 뜻이므로 남은 시간을 말하지 않는다.\n */\nfunction statusRenderer(durationMinutes: () => number) {\n return (value: string, _column: any, record: any) => {\n if (value === 'locked') {\n return html`<span class=\"badge\" attention\n >${i18next.t('label.locked-user')}${lockLeft(record, durationMinutes())}</span\n >`\n }\n\n if (value === 'inactive') {\n return html`<span class=\"badge\" quiet>${i18next.t('label.inactive-user')}</span>`\n }\n\n /* 활성은 기본값이다 — 색을 쓰지 않는다. 색은 「무엇이 나를 기다리나」에만 쓴다. */\n return html`<span class=\"badge\">${i18next.t('label.activated-user', { defaultValue: value })}</span>`\n }\n}\n\n/** 잠금이 언제 풀리는지. 판정은 서버가 하고 여기서는 남은 시간만 말한다. */\nfunction lockLeft(record: any, minutes: number) {\n const lockedAt = record?.lockedAt ? new Date(record.lockedAt).getTime() : NaN\n\n if (Number.isNaN(lockedAt) || !(minutes > 0)) {\n /* `lockedAt` 이 없으면 자동 해제가 없다는 뜻이다 — 남은 시간을 지어내지 않는다. */\n return nothing\n }\n\n const left = Math.ceil((lockedAt + minutes * 60 * 1000 - Date.now()) / 60000)\n\n return left > 0 ? html` · ${i18next.t('text.unlocks in x minutes', { x: left })}` : nothing\n}\n\n/** 역할이 없는 것은 빈 칸이 아니라 **문장**으로 말한다. */\nfunction rolesRenderer(_value: any, _column: any, record: any) {\n const roles = record?.roles || []\n\n return roles.length\n ? html`${roles.map((role: any) => role.name).join(' · ')}`\n : html`<span class=\"badge\" attention>${i18next.t('text.no role assigned')}</span>`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"user-list-page.js","sourceRoot":"","sources":["../../../client/pages/user/user-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/list-state/ox-empty-note.js'\nimport '@operato/list-state/ox-page-state.js'\nimport '../../components/user-detail.js'\nimport '../../components/user-invite.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { PageView } from '@operato/shell'\nimport {\n CommonButtonStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n WorkSurfacePageStyles\n} from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 사용자 목록 — **서버가 거르고, 서버가 쪽을 나눈다.**\n *\n * ── 옛 화면이 하던 것 (`pages/user/user-management.ts`) ──────────────────────\n * `query { users { items { … } } }` 로 **도메인의 모든 사용자**를 한 번에 가져왔다. 한도도\n * 서버 검색도 없었고, 프레임워크가 이미 그것을 경고하고 있었다\n * (`unbounded list query on \"User\"`). 사용자는 권한(84행)과 달리 실제로 늘어난다.\n *\n * 그런데 **서버는 처음부터 받을 준비가 되어 있었다** — `users(filters:, pagination:, sortings:)`\n * 가 `ListParam` 을 받고 `name`·`email`·`description` 을 검색어로 본다\n * (`user-query.ts`). 화면이 안 쓰고 있었을 뿐이다.\n *\n * ── 왜 `ox-grist` 인가 ──────────────────────────────────────────────────────\n * 손으로 만든 목록이 잃는 것을 그것이 이미 갖고 있다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 사람마다 기억하는 개인 설정, 카드↔표 모드, 내보내기. `figure-list-page` 가 같은 이유로\n * 옮겼고 그 판단을 그대로 따른다.\n *\n * ── 옛 화면의 탭을 없앤 이유 ────────────────────────────────────────────────\n * 탭이 `userType` 값으로 갈렸다. 그런데 그 칸은 `varchar` 이고 코드가 예상하는 넷\n * (`admin`·`user`·`application`·`appliance`) 밖의 값이거나 비어 있으면 **그 사람이 어느 탭에도\n * 안 나온다 — 화면에서 조용히 사라진다.**\n *\n * 여기서는 사람만 싣는다. 애플리케이션과 어플라이언스는 자기 화면(`applications`·`appliance`)에\n * 있고, 거기서 자기 자격증명까지 함께 다루는 것이 맞다. 다만 **거르는 것은 서버가 해야 한다** —\n * 지금은 `users` 가 종류를 안 가리므로 이 화면이 filter 로 요구한다.\n */\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정(모드·정렬·쪽 크기를 사람마다\n * 기억하는 것)을 붙일 수 없다. `figure-list-page` 가 같은 주석을 달고 있다.\n */\nconst UserListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('user-list-page')\nexport class UserListPage extends UserListPageBase {\n static styles: CSSResultGroup = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n /* The note below is laid out against the page, over the grid. */\n :host {\n position: relative;\n }\n\n /*\n ── 상태 배지 ──────────────────────────────────────────────────────────\n 색은 **짝으로** 쓴다(container / on-container). 한쪽만 쓰면 야간에 대비가 깨진다.\n 그리고 색은 「무엇이 나를 기다리나」에만 쓴다 — 활성은 기본값이라 색이 없다.\n 담는 면이 행이므로 모서리는 작게 잡는다.\n */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n .badge[quiet] {\n color: var(--md-sys-color-outline);\n }\n\n /* 격자를 떼지 않고 위에 겹친다 — 격자가 살아 있어야 다시 읽을 수 있다. */\n div[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--ui-bg, var(--md-sys-color-surface));\n }\n `\n ]\n\n @property({ type: Object }) config: any\n /*\n * 카드가 아니라 **행**이다. 카드는 그림으로 알아보는 물건(형상 카탈로그)의 모양이고,\n * 사람은 이름으로 알아본다. 좁은 화면에서는 `LIST` 가 같은 행을 접어 그린다.\n */\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n @state() private lockDurationMinutes: number = 10\n\n /*\n * **읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다.** 셋을 한 문장으로 그리면 못 읽은 것을\n * 「없다」로 읽고, 사용자가 이미 있는 것을 만들러 간다.\n */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n /** Why the read failed. The screen shows the server's own sentence. */\n @state() private loadError: unknown = null\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n /*\n * Calling someone in is what this screen is for, and it belongs in the page action bar\n * (ADR-0060 decision 9). It used to float over the list. A button that does the same thing in a\n * different place on every screen makes what you learned on one screen useless on the next.\n * Which of the two acts it is - adding an account that already exists, or sending an invitation\n * - is said by the window it opens.\n */\n return {\n title: i18next.t('text.user management'),\n help: 'auth/users',\n /*\n * Full text search sits in the title bar, the way every other list screen in these\n * applications carries it (user instruction, 2026-09-18). It searches the columns that\n * declare a search filter - here the name and the address.\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n /* Narrowing by state stays in the headroom, and this folds it. */\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n {\n title: i18next.t('label.add user'),\n action: () => this.onInvite(),\n ...CommonButtonStyles.submit,\n /* The mark says what the button does: call someone in, rather than submit. */\n icon: 'person_add'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\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 <div class=\"filters\">\n <ox-filters-form without-search autofocus></ox-filters-form>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n `\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * 거른 결과가 비어 있는 것은 **가르칠 자리가 아니다.** 그때는 격자가 스스로 「없음」을\n * 말하게 두고, 여기서는 아무것도 그리지 않는다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n /*\n Tells a refusal from a breakage, carries the server's own sentence, and offers to read it\n again (decision 8). It used to be one note for everything: \"failed to fetch, try again\".\n */\n return html`<div note>\n <ox-page-state .error=${this.loadError} @retry=${() => this.grist.fetch()}></ox-page-state>\n </div>`\n }\n\n if (this.total === 0 && !this.filtered) {\n /* 새 도메인의 첫 화면. 소유자는 역할 없이도 모두 열리므로 그 사실부터 말한다. */\n return html`<div note>\n <ox-empty-note\n icon=\"group_add\"\n title=${i18next.t('text.no other user yet')}\n description=${i18next.t('text.owner opens everything without a role')}\n ></ox-empty-note>\n </div>`\n }\n\n return nothing\n }\n\n async pageInitialized() {\n this.config = {\n /*\n * 카드에 실을 것. 표 모드도 같은 컬럼 설정을 쓰므로 **두 모드가 다른 말을 하지 않는다.**\n */\n list: {\n fields: ['name', 'email'],\n details: ['status', 'roles']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { align: 'left', renderer: nameWithOwnerMark },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { align: 'left' },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * 상태 하나로 두 가지를 말한다 — 지금 상태와, 잠긴 경우 **언제 풀리는지**.\n * `lockedAt` 과 설정된 잠금 시간으로 계산한다. 옛 화면은 「잠김」까지만 말했고,\n * 그래서 관리자가 기다리면 되는지 풀어 줘야 하는지 알 수 없었다.\n */\n type: 'string',\n name: 'status',\n header: i18next.t('field.status'),\n record: { align: 'center', renderer: statusRenderer(() => this.lockDurationMinutes) },\n /*\n * The editor a filter gets comes from the filter's own `type`, and without one the form\n * falls back to a text box - so this list of states was declared and never drawn. The\n * states are shown by the same words the cell uses, not by their stored spelling.\n */\n filter: {\n type: 'select',\n options: [\n { display: '', value: '' },\n { display: i18next.t('label.activated-user'), value: 'activated' },\n { display: i18next.t('label.locked-user'), value: 'locked' },\n { display: i18next.t('label.inactive-user'), value: 'inactive' }\n ],\n operator: 'eq'\n },\n sortable: true,\n width: 140\n },\n {\n /*\n * 역할이 비어 있는 것을 **눈에 띄게** 그린다. 역할 없는 활성 계정은 로그인은 되는데\n * 화면이 하나도 없다 — 옛 화면에서는 그것이 빈 칸이라 아무도 알아채지 못했다.\n */\n type: 'string',\n name: 'roles',\n header: i18next.t('field.roles'),\n record: { align: 'left', renderer: rolesRenderer },\n width: 260\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n /* Read only here - a new one is made elsewhere, so the blank row at the end offers nothing. */\n\n appendable: false,\n selectable: { multiple: false },\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => this.onSelectUser(record)\n }\n },\n sorters: [{ name: 'name' }]\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 /**\n * 사람만 가져온다. **거르는 것은 서버가 한다** — 옛 화면은 전부 받아 클라이언트에서 나눴다.\n *\n * `userType` 은 `varchar` 라 값이 넷 밖으로 나갈 수 있다. 그래서 「기계가 아닌 것」을\n * `not-in` 으로 요구한다 — 「사람인 것」을 `in` 으로 요구하면 값이 비어 있는 사람이 사라진다.\n * (`userType` 을 enum 으로 못 박는 것은 §3 의 일이다.)\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n this.loadError = null\n /* 사람만 보려고 이 화면이 얹는 조건은 「거른 것」으로 세지 않는다 — 그것까지 세면\n 아무도 없는 도메인이 「거른 결과가 없다」로 보여 첫 안내가 안 나온다. */\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: users(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n username\n name\n email\n userType\n status\n lockedAt\n owner\n roles {\n id\n name\n }\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n ...filters,\n {\n name: 'userType',\n operator: 'notin',\n value: ['application', 'appliance']\n }\n ],\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }]\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n this.loadError = e\n throw e\n }\n }\n\n /**\n * 고른 사람의 역할을 고치는 자리를 연다.\n *\n * `domain-management` 가 격자 행에서 `ox-attribute-view` 를 여는 것과 같은 모양이다 —\n * `handlers.click` 에서 `openPopup` 에 template 을 넘긴다.\n *\n * `escapable` 은 넘기지 않는다. 이 앱의 기본값이 `false` 이고, 그것이 곧 **ESC 에 먼저\n * 묻는다**는 뜻이다 — 편집면에서 원하는 거동이 기본값이다.\n */\n private onSelectUser(record: any) {\n const popup = openPopup(\n html`\n <user-detail\n .user=${record}\n @close=${() => popup.close()}\n @user-updated=${() => {\n popup.close()\n /* 고친 사람만 다시 읽을 길이 없다 — 격자가 가진 것은 쪽 단위 새로고침뿐이다. */\n this.grist.fetch()\n }}\n ></user-detail>\n `,\n {\n backdrop: true,\n size: 'large',\n title: record?.name || record?.username || record?.email\n }\n )\n }\n\n /** 목록의 역할 편집과 같은 모양으로 연다 — `openPopup` 에 template 을 넘긴다. */\n private onInvite() {\n const popup = openPopup(\n html`\n <user-invite\n @close=${() => popup.close()}\n @user-invited=${() => {\n popup.close()\n this.grist.fetch()\n }}\n ></user-invite>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('label.add user')\n }\n )\n }\n}\n\n/** 소유자는 이름 옆에 표시한다. 서버가 사람마다 `owner` 를 채워 준다(공동 소유자도 함께). */\nfunction nameWithOwnerMark(value: string, _column: any, record: any) {\n return record?.owner\n ? html`<md-icon style=\"--md-icon-size: 16px; vertical-align: -3px;\">supervisor_account</md-icon> ${value}`\n : html`${value}`\n}\n\n/**\n * 잠긴 계정이 **언제 풀리는지**까지 말한다.\n *\n * 판정은 서버(`account-lock-rule.ts`)가 하고 여기서는 남은 시간만 보여 준다 —\n * 화면이 판정을 다시 하면 두 곳이 다른 답을 낸다. `lockedAt` 이 비어 있으면 자동 해제가\n * 없다는 뜻이므로 남은 시간을 말하지 않는다.\n */\nfunction statusRenderer(durationMinutes: () => number) {\n return (value: string, _column: any, record: any) => {\n if (value === 'locked') {\n return html`<span class=\"badge\" attention\n >${i18next.t('label.locked-user')}${lockLeft(record, durationMinutes())}</span\n >`\n }\n\n if (value === 'inactive') {\n return html`<span class=\"badge\" quiet>${i18next.t('label.inactive-user')}</span>`\n }\n\n /* 활성은 기본값이다 — 색을 쓰지 않는다. 색은 「무엇이 나를 기다리나」에만 쓴다. */\n return html`<span class=\"badge\">${i18next.t('label.activated-user', { defaultValue: value })}</span>`\n }\n}\n\n/** 잠금이 언제 풀리는지. 판정은 서버가 하고 여기서는 남은 시간만 말한다. */\nfunction lockLeft(record: any, minutes: number) {\n const lockedAt = record?.lockedAt ? new Date(record.lockedAt).getTime() : NaN\n\n if (Number.isNaN(lockedAt) || !(minutes > 0)) {\n /* `lockedAt` 이 없으면 자동 해제가 없다는 뜻이다 — 남은 시간을 지어내지 않는다. */\n return nothing\n }\n\n const left = Math.ceil((lockedAt + minutes * 60 * 1000 - Date.now()) / 60000)\n\n return left > 0 ? html` · ${i18next.t('text.unlocks in x minutes', { x: left })}` : nothing\n}\n\n/** 역할이 없는 것은 빈 칸이 아니라 **문장**으로 말한다. */\nfunction rolesRenderer(_value: any, _column: any, record: any) {\n const roles = record?.roles || []\n\n return roles.length\n ? html`${roles.map((role: any) => role.name).join(' · ')}`\n : html`<span class=\"badge\" attention>${i18next.t('text.no role assigned')}</span>`\n}\n"]}
|