From 5bb75cb753afdd6a4764d91fc7eb84d40a5397f6 Mon Sep 17 00:00:00 2001 From: SungChul Hong Date: Wed, 16 Oct 2024 17:43:39 +0900 Subject: [PATCH] feat: remove legacy user creation modal --- src/components/backend-ai-credential-list.ts | 1466 ------------------ src/components/backend-ai-credential-view.ts | 902 ----------- src/components/backend-ai-user-list.ts | 753 --------- 3 files changed, 3121 deletions(-) delete mode 100644 src/components/backend-ai-credential-list.ts delete mode 100644 src/components/backend-ai-credential-view.ts delete mode 100644 src/components/backend-ai-user-list.ts diff --git a/src/components/backend-ai-credential-list.ts b/src/components/backend-ai-credential-list.ts deleted file mode 100644 index ab396cf9ef..0000000000 --- a/src/components/backend-ai-credential-list.ts +++ /dev/null @@ -1,1466 +0,0 @@ -/** - @license - Copyright (c) 2015-2024 Lablup Inc. All rights reserved. - */ -import '../plastics/lablup-shields/lablup-shields'; -import { - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, -} from '../plastics/layout/iron-flex-layout-classes'; -import BackendAIDialog from './backend-ai-dialog'; -import { BackendAiStyles } from './backend-ai-general-styles'; -import BackendAIListStatus, { StatusCondition } from './backend-ai-list-status'; -import './backend-ai-list-status'; -import { BackendAIPage } from './backend-ai-page'; -import { default as PainKiller } from './backend-ai-painkiller'; -import './lablup-grid-sort-filter-column'; -import '@material/mwc-button/mwc-button'; -import '@material/mwc-list/mwc-list-item'; -import { Select } from '@material/mwc-select/mwc-select'; -import { TextField } from '@material/mwc-textfield/mwc-textfield'; -import '@vaadin/grid/vaadin-grid'; -import '@vaadin/grid/vaadin-grid-filter-column'; -import '@vaadin/grid/vaadin-grid-sort-column'; -import '@vaadin/icons/vaadin-icons'; -import '@vaadin/item/vaadin-item'; -import { css, CSSResultGroup, html, render } from 'lit'; -import { get as _text, translate as _t } from 'lit-translate'; -import { customElement, property, query } from 'lit/decorators.js'; - -/** - Backend.AI Credential List - - @group Backend.AI Web UI - @element backend-ai-credential-list - */ - -class UnableToDeleteKeypairException extends Error { - public title: string; - - constructor(message: string) { - super(message); - Object.setPrototypeOf(this, UnableToDeleteKeypairException.prototype); - this.title = 'Unable to delete keypair'; - } -} - -@customElement('backend-ai-credential-list') -export default class BackendAICredentialList extends BackendAIPage { - @property({ type: Object }) notification; - @property({ type: Object }) keypairInfo = { - user_id: '1', - access_key: 'ABC', - secret_key: 'ABC', - last_used: '', - is_admin: false, - resource_policy: '', - rate_limit: 5000, - concurrency_used: 0, - num_queries: 0, - created_at: '', - }; - @property({ type: Boolean }) isAdmin = false; - @property({ type: String }) condition = 'active'; - @property({ type: Array }) keypairs = []; - @property({ type: Object }) resourcePolicy = Object(); - @property({ type: Object }) indicator = Object(); - @property({ type: Object }) _boundKeyageRenderer = - this.keyageRenderer.bind(this); - @property({ type: Object }) _boundControlRenderer = - this.controlRenderer.bind(this); - @property({ type: Object }) _boundAccessKeyRenderer = - this.accessKeyRenderer.bind(this); - @property({ type: Object }) _boundPermissionRenderer = - this.permissionRenderer.bind(this); - @property({ type: Object }) _boundResourcePolicyRenderer = - this.resourcePolicyRenderer.bind(this); - @property({ type: Object }) _boundAllocationRenderer = - this.allocationRenderer.bind(this); - @property({ type: Object }) _boundUserIdRenderer = - this.userIdRenderer.bind(this); - @property({ type: Object }) keypairGrid = Object(); - @property({ type: String }) listCondition: StatusCondition = 'loading'; - @property({ type: Number }) _totalCredentialCount = 0; - @property({ type: Boolean }) isUserInfoMaskEnabled = false; - @property({ type: String }) deleteKeyPairUserName = ''; - @property({ type: String }) deleteKeyPairAccessKey = ''; - @property({ type: Boolean }) supportMainAccessKey = false; - @property({ type: Array }) _mainAccessKeyList: string[] = []; - @query('#keypair-info-dialog') keypairInfoDialog!: BackendAIDialog; - @query('#keypair-modify-dialog') keypairModifyDialog!: BackendAIDialog; - @query('#delete-keypair-dialog') deleteKeyPairDialog!: BackendAIDialog; - @query('#policy-list') policyListSelect!: Select; - @query('#rate-limit') rateLimit!: TextField; - @query('#list-status') private _listStatus!: BackendAIListStatus; - - constructor() { - super(); - } - - static get styles(): CSSResultGroup { - return [ - BackendAiStyles, - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, - // language=CSS - css` - vaadin-grid { - border: 0; - font-size: 14px; - height: calc(100vh - 229px); - } - - mwc-icon-button { - --mdc-icon-size: 24px; - padding: 0; - } - - mwc-icon { - --mdc-icon-size: 16px; - padding: 0; - } - - vaadin-item { - font-size: 13px; - font-weight: 100; - } - - vaadin-item div[secondary] { - font-weight: 400; - } - - div.indicator, - span.indicator { - font-size: 9px; - margin-right: 5px; - } - - div.configuration { - width: 100px !important; - } - - div.configuration mwc-icon { - padding-right: 5px; - } - - mwc-list-item { - width: var(--token-mwc-select-item-width, 340px); - } - - backend-ai-dialog { - --component-min-width: 400px; - } - - backend-ai-dialog h4 { - font-size: 14px; - padding: 5px 15px 5px 12px; - margin: 0 0 10px 0; - display: block; - height: 20px; - border-bottom: 1px solid var(--token-colorBorder, #ccc); - } - - mwc-button, - mwc-button[unelevated], - mwc-button[outlined] { - background-image: none; - --mdc-theme-primary: var(--general-button-background-color); - --mdc-theme-on-primary: var(--general-button-color); - --mdc-typography-font-family: var(--token-fontFamily); - } - `, - ]; - } - - firstUpdated() { - this.notification = globalThis.lablupNotification; - } - - /** - * Check the admin and set the keypair grid when backend.ai client connected. - * - * @param {Booelan} active - The component will work if active is true. - */ - async _viewStateChanged(active: boolean) { - await this.updateComplete; - if (active === false) { - return; - } - // If disconnected - if ( - typeof globalThis.backendaiclient === 'undefined' || - globalThis.backendaiclient === null || - globalThis.backendaiclient.ready === false - ) { - document.addEventListener( - 'backend-ai-connected', - () => { - this._refreshKeyData(); - this.isAdmin = globalThis.backendaiclient.is_admin; - this.supportMainAccessKey = - globalThis.backendaiclient.supports('main-access-key'); - this.isUserInfoMaskEnabled = - globalThis.backendaiclient._config.maskUserInfo; - this.keypairGrid = this.shadowRoot?.querySelector('#keypair-grid'); - }, - true, - ); - } else { - // already connected - this._refreshKeyData(); - this.isAdmin = globalThis.backendaiclient.is_admin; - this.supportMainAccessKey = - globalThis.backendaiclient.supports('main-access-key'); - this.isUserInfoMaskEnabled = - globalThis.backendaiclient._config.maskUserInfo; - this.keypairGrid = this.shadowRoot?.querySelector('#keypair-grid'); - } - } - - /** - * Refresh key data when user id is null. - * - * @param {string} user_id - * @return {void} - */ - _refreshKeyData(user_id: null | string = null) { - let is_active = true; - switch (this.condition) { - case 'active': - is_active = true; - break; - default: - is_active = false; - } - this.listCondition = 'loading'; - this._listStatus?.show(); - return globalThis.backendaiclient.resourcePolicy - .get() - .then((response) => { - const rp = response.keypair_resource_policies; - this.resourcePolicy = globalThis.backendaiclient.utils.gqlToObject( - rp, - 'name', - ); - }) - .then(() => { - const fields = [ - 'access_key', - 'is_active', - 'is_admin', - 'user_id', - 'created_at', - 'last_used', - 'concurrency_limit', - 'concurrency_used', - 'rate_limit', - 'num_queries', - 'resource_policy', - ]; - return globalThis.backendaiclient.keypair.list( - user_id, - fields, - is_active, - ); - }) - .then(async (response) => { - if (this.supportMainAccessKey) { - try { - // since accesskey and user account status doesn't match all the time, - // therefore we need to query from both active and inactive user - const activeUserMainAccessKeyList = - await globalThis.backendaiclient.user.list(true, [ - 'main_access_key', - ]); - const inactiveUserMainAccessKeyList = - await globalThis.backendaiclient.user.list(false, [ - 'main_access_key', - ]); - if ( - activeUserMainAccessKeyList.users && - inactiveUserMainAccessKeyList.users - ) { - this._mainAccessKeyList = [ - ...activeUserMainAccessKeyList.users, - ...inactiveUserMainAccessKeyList.users, - ].map((userInfo) => userInfo.main_access_key); - } - } catch (err) { - throw err; - } - } - const keypairs = response.keypairs; - Object.keys(keypairs).map((objectKey, index) => { - const keypair = keypairs[objectKey]; - if (keypair.resource_policy in this.resourcePolicy) { - for (const k in this.resourcePolicy[keypair.resource_policy]) { - if (k === 'created_at') { - continue; - } - keypair[k] = this.resourcePolicy[keypair.resource_policy][k]; - if (k === 'total_resource_slots') { - keypair['total_resource_slots'] = JSON.parse( - this.resourcePolicy[keypair.resource_policy][k], - ); - } - } - keypair['created_at_formatted'] = this._humanReadableTime( - keypair['created_at'], - ); - keypair['elapsed'] = this._elapsed(keypair['created_at']); - if ('cpu' in keypair['total_resource_slots']) { - } else if (keypair['default_for_unspecified'] === 'UNLIMITED') { - keypair['total_resource_slots'].cpu = '-'; - } - if ('mem' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].mem = parseFloat( - globalThis.backendaiclient.utils.changeBinaryUnit( - keypair['total_resource_slots'].mem, - 'g', - ), - ); - // keypair['total_resource_slots'].mem = parseFloat(keypair['total_resource_slots'].mem); - } else if (keypair['default_for_unspecified'] === 'UNLIMITED') { - keypair['total_resource_slots'].mem = '-'; - } - if ('cuda.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].cuda_device = - keypair['total_resource_slots']['cuda.device']; - } - if ('cuda.shares' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].cuda_shares = - keypair['total_resource_slots']['cuda.shares']; - } - if ( - 'cuda_device' in keypair['total_resource_slots'] === false && - 'cuda_shares' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].cuda_shares = '-'; - keypair['total_resource_slots'].cuda_device = '-'; - } - if ('rocm.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].rocm_device = - keypair['total_resource_slots']['rocm.device']; - } - - if ( - 'rocm_device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].rocm_device = '-'; - } - if ('tpu.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].tpu_device = - keypair['total_resource_slots']['tpu.device']; - } - if ( - 'tpu_device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].tpu_device = '-'; - } - if ('ipu.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].ipu_device = - keypair['total_resource_slots']['ipu.device']; - } - if ( - 'ipu_device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].ipu_device = '-'; - } - if ('atom.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].atom_device = - keypair['total_resource_slots']['atom.device']; - } - if ( - 'atom_device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].atom_device = '-'; - } - if ('atom-plus.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].atom_plus_device = - keypair['total_resource_slots']['atom-plus.device']; - } - if ( - 'atom-plus.device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].atom_plus_device = '-'; - } - if ('gaudi2.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].gaudi2_device = - keypair['total_resource_slots']['gaudi2.device']; - } - if ( - 'gaudi2.device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].gaudi2_device = '-'; - } - if ('warboy.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].warboy_device = - keypair['total_resource_slots']['warboy.device']; - } - if ( - 'warboy_device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].warboy_device = '-'; - } - if ('rngd.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].rngd_device = - keypair['total_resource_slots']['rngd.device']; - } - if ( - 'rngd.device' in keypair['total_resource_slots'] === false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].rngd_device = '-'; - } - if ('hyperaccel-lpu.device' in keypair['total_resource_slots']) { - keypair['total_resource_slots'].hyperaccel_lpu_device = - keypair['total_resource_slots']['hyperaccel-lpu.device']; - } - if ( - 'hyperaccel_lpu_device' in keypair['total_resource_slots'] === - false && - keypair['default_for_unspecified'] === 'UNLIMITED' - ) { - keypair['total_resource_slots'].hyperaccel_lpu_device = '-'; - } - - [ - 'cpu', - 'mem', - 'cuda_shares', - 'cuda_device', - 'rocm_device', - 'tpu_device', - 'ipu_device', - 'atom_device', - 'atom_plus_device', - 'gaudi2_device', - 'warboy_device', - 'rngd_device', - 'hyperaccel_lpu_device', - ].forEach((slot) => { - keypair['total_resource_slots'][slot] = this._markIfUnlimited( - keypair['total_resource_slots'][slot], - ); - }); - keypair['max_vfolder_size'] = this._markIfUnlimited( - BackendAICredentialList.bytesToGB(keypair['max_vfolder_size']), - ); - } - }); - this.keypairs = keypairs; - if (this.keypairs.length == 0) { - this.listCondition = 'no-data'; - } else { - this._listStatus?.hide(); - } - // setTimeout(() => { this._refreshKeyData(status) }, 5000); - }) - .catch((err) => { - this._listStatus?.hide(); - console.log(err); - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - }); - } - - /** - * Display a keypair information dialog. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - async _showKeypairDetail(e) { - const controls = e.target.closest('#controls'); - const access_key = controls['access-key']; - try { - const data = await this._getKeyData(access_key); - this.keypairInfo = data.keypair; - this.keypairInfoDialog.show(); - } catch (err) { - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - } - } - - /** - * Modify resource policy by displaying keypair modify dialog. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - async _modifyResourcePolicy(e) { - const controls = e.target.closest('#controls'); - const access_key = controls['access-key']; - try { - const data = await this._getKeyData(access_key); - this.keypairInfo = data.keypair; - - this.policyListSelect.value = this.keypairInfo.resource_policy; - this.rateLimit.value = this.keypairInfo.rate_limit.toString(); - - this.keypairModifyDialog.show(); - } catch (err) { - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - } - } - - /** - * Get key data from access key. - * - * @param {string} accessKey - access key to query - */ - async _getKeyData(accessKey) { - const fields = [ - 'access_key', - 'secret_key', - 'is_active', - 'is_admin', - 'user_id', - 'created_at', - 'last_used', - 'concurrency_limit', - 'concurrency_used', - 'rate_limit', - 'num_queries', - 'resource_policy', - ]; - return globalThis.backendaiclient.keypair.info(accessKey, fields); - } - - /** - * Refresh the key data. - */ - refresh() { - this._refreshKeyData(); - } - - /** - * Return the condtion is active. - * - * @return {boolean} condition - boolean whether the current component condition is active or not. - */ - _isActive() { - return this.condition === 'active'; - } - - /** - * Show the keypair detail dialog. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - _deleteKeyPairDialog(e) { - const controls = e.target.closest('#controls'); - const user_id = controls['user-id']; - const access_key = controls['access-key']; - this.deleteKeyPairUserName = user_id; - this.deleteKeyPairAccessKey = access_key; - this.deleteKeyPairDialog.show(); - } - - /** - * Delete the access key. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - _deleteKey(e) { - globalThis.backendaiclient.keypair - .delete(this.deleteKeyPairAccessKey) - .then((response) => { - if (response.delete_keypair && !response.delete_keypair.ok) { - throw new UnableToDeleteKeypairException(response.delete_keypair.msg); - } - this.notification.text = _text('credential.KeySeccessfullyDeleted'); - this.notification.show(); - this.refresh(); - this.deleteKeyPairDialog.hide(); - }) - .catch((err) => { - console.log(err); - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - }); - } - - /** - * Revoke the access key. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - _revokeKey(e) { - this._mutateKey(e, false); - } - - /** - * Reuse the access key. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - */ - _reuseKey(e) { - this._mutateKey(e, true); - } - - /** - * Mutate the access key. - * - * @param {Event} e - Dispatches from the native input event each time the input changes. - * @param {Boolean} is_active - */ - _mutateKey(e, is_active: boolean) { - const controls = e.target.closest('#controls'); - const accessKey = controls['access-key']; - const original: any = this.keypairs.find(this._findKeyItem, accessKey); - const input = { - is_active: is_active, - is_admin: original.is_admin, - resource_policy: original.resource_policy, - rate_limit: original.rate_limit, - concurrency_limit: original.concurrency_limit, - }; - globalThis.backendaiclient.keypair - .mutate(accessKey, input) - .then((response) => { - const event = new CustomEvent('backend-ai-credential-refresh', { - detail: this, - }); - document.dispatchEvent(event); - }) - .catch((err) => { - console.log(err); - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - }); - } - - /** - * Find the access key. - * - * @param {Record} element - * @return {boolean} - Return whether the access key is same as this credential list's or not. - */ - _findKeyItem(element) { - return (element.access_key = this); - } - - /** - * Return backend.ai client elapsed time. - * - * @param {Date} start - Start time of backend.ai client. - * @param {Date} end - End time of backend.ai client. - * @return {string} days - Elapsed days - */ - _elapsed(start, end?) { - const startDate = new Date(start); - const endDate = this.condition == 'active' ? new Date() : new Date(); - const seconds = Math.floor( - (endDate.getTime() - startDate.getTime()) / 1000, - ); - const days = Math.floor(seconds / 86400); - return days; - } - - /** - * Change d of any type to human readable date time. - * - * @param {Date} d - string or DateTime object to convert - * @return {Date} - Formatted date / time to be human-readable text. - */ - _humanReadableTime(d) { - return new Date(d).toUTCString(); - } - - /** - * Render an index. - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param {object} rowData - */ - _indexRenderer(root, column, rowData) { - const idx = rowData.index + 1; - render( - html` -
${idx}
- `, - root, - ); - } - - /** - * If value includes unlimited contents, mark as unlimited. - * - * @param {string} value - value to check - * @return {string} - Unlimited character is value is unlimited. - */ - _markIfUnlimited(value) { - if (['-', 0, 'Unlimited', Infinity, 'Infinity'].includes(value)) { - return '∞'; - } else { - return value; - } - } - - /** - * Render a key elasped time. - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param {object} rowData - */ - keyageRenderer(root, column?, rowData?) { - render( - html` -
- ${rowData.item.elapsed} ${_t('credential.Days')} - (${rowData.item.created_at_formatted}) -
- `, - root, - ); - } - - /** - * Render key control buttons. - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param {object} rowData - */ - controlRenderer(root, column?, rowData?) { - render( - html` -
- - - ${this.isAdmin && this._isActive() - ? html` - - ` - : html``} - ${this._isActive() === false - ? html` - - ` - : html``} - ${this.isAdmin && !this._isActive() - ? html` - - ` - : html``} -
- `, - root, - ); - } - - /** - * Render accesskey column according to configuration - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param rowData - */ - accessKeyRenderer(root, column?, rowData?) { - render( - // language=HTML - html` -
-
${rowData.item.access_key}
- ${this._mainAccessKeyList.includes(rowData.item?.access_key) - ? html` - - ` - : html``} -
- `, - root, - ); - } - - /** - * Render permission(role) column - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param rowData - */ - permissionRenderer(root, column?, rowData?) { - render( - // language=HTML - html` -
- ${rowData.item.is_admin - ? html` - - ` - : html``} - -
- `, - root, - ); - } - - /** - * Render resourcePolicy column - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param rowData - */ - resourcePolicyRenderer(root, column?, rowData?) { - render( - // language=HTML - html` -
- ${rowData.item.resource_policy} -
-
-
- developer_board - ${rowData.item.total_resource_slots.cpu} - ${_t('general.cores')} -
-
- memory - ${rowData.item.total_resource_slots.mem} - GiB -
-
-
- ${rowData.item.total_resource_slots.cuda_device - ? html` -
- view_module - ${rowData.item.total_resource_slots.cuda_device} - GPU -
- ` - : html``} - ${rowData.item.total_resource_slots.cuda_shares - ? html` -
- view_module - ${rowData.item.total_resource_slots.cuda_shares} - fGPU -
- ` - : html``} -
- ${!globalThis.backendaiclient.supports( - 'deprecated-max-vfolder-count-in-keypair-resource-policy', - ) - ? html` -
-
- cloud_queue - ${rowData.item.max_vfolder_size} - GB -
-
-
- folder - ${rowData.item.max_vfolder_count} - ${_t('general.Folders')} -
- ` - : html``} - - `, - root, - ); - } - - /** - * Render allocation column - * - * @param {DOMelement} root - * @param {object} column ( element) - * @param rowData - */ - allocationRenderer(root, column?, rowData?) { - render( - // language=HTML - html` -
-
-
- ${rowData.item.concurrency_used} / - ${rowData.item.concurrency_limit} -
- Sess. -
-
- - ${rowData.item.rate_limit} - req./15m. - - - ${rowData.item.num_queries} - queries - -
-
- `, - root, - ); - } - - /** - * Render userId according to configuration - * - * @param {DOMelement} root - * @param {object} column - * @param {object} rowData - */ - userIdRenderer(root, column?, rowData?) { - render( - // language=HTML - html` - ${this._getUserId(rowData.item.user_id)} - `, - root, - ); - } - - _validateRateLimit() { - // this._adjustRateLimit(); - const warningRateLimit = 100; - const maximumRateLimit = 50000; // the maximum value of rate limit value - - this.rateLimit.validityTransform = (newValue, nativeValidity) => { - if (!nativeValidity.valid) { - if (nativeValidity.valueMissing) { - this.rateLimit.validationMessage = _text( - 'credential.RateLimitInputRequired', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } else if (nativeValidity.rangeOverflow) { - this.rateLimit.value = newValue = maximumRateLimit.toString(); - this.rateLimit.validationMessage = _text( - 'credential.RateLimitValidation', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } else if (nativeValidity.rangeUnderflow) { - this.rateLimit.value = newValue = '1'; - this.rateLimit.validationMessage = _text( - 'credential.RateLimitValidation', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } else { - this.rateLimit.validationMessage = _text( - 'credential.InvalidRateLimitValue', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - } else { - if ( - newValue.length !== 0 && - !isNaN(Number(newValue)) && - Number(newValue) < warningRateLimit - ) { - this.rateLimit.validationMessage = _text( - 'credential.WarningLessRateLimit', - ); - return { - valid: !nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - }; - } - - openDialog(id) { - (this.shadowRoot?.querySelector('#' + id) as BackendAIDialog).show(); - } - - closeDialog(id) { - (this.shadowRoot?.querySelector('#' + id) as BackendAIDialog).hide(); - } - - /** - * Save a keypair modification. - * - * @param {boolean} confirm - Save keypair info even if rateLimit is less the warningRateLimit if `confirm` is true. - */ - _saveKeypairModification(confirm = false) { - const resourcePolicy = this.policyListSelect.value; - const rateLimit = Number(this.rateLimit.value); - const warningRateLimit = 100; - - if (!this.rateLimit.checkValidity()) { - if (rateLimit < warningRateLimit && confirm) { - // Do nothing - } else if (rateLimit < warningRateLimit && !confirm) { - this.openDialog('keypair-confirmation'); - return; - } else { - return; - } - } - - let input = {}; - if (resourcePolicy !== this.keypairInfo.resource_policy) { - input = { ...input, resource_policy: resourcePolicy }; - } - if (rateLimit !== this.keypairInfo.rate_limit) { - input = { ...input, rate_limit: rateLimit }; - } - - if (Object.entries(input).length === 0) { - this.notification.text = _text('credential.NoChanges'); - this.notification.show(); - } else { - globalThis.backendaiclient.keypair - .mutate(this.keypairInfo.access_key, input) - .then((res) => { - if (res.modify_keypair.ok) { - if ( - this.keypairInfo.resource_policy === resourcePolicy && - this.keypairInfo.rate_limit === rateLimit - ) { - this.notification.text = _text('credential.NoChanges'); - } else { - this.notification.text = _text( - 'environment.SuccessfullyModified', - ); - } - this.refresh(); - } else { - this.notification.text = _text('dialog.ErrorOccurred'); - } - this.notification.show(); - }); - } - this.closeDialog('keypair-modify-dialog'); - } - - _confirmAndSaveKeypairModification() { - this.closeDialog('keypair-confirmation'); - this._saveKeypairModification(true); - } - - /** - * Adjust Rate Limit value below the maximum value (50000) and also upper than zero. - * - */ - _adjustRateLimit() { - const maximumRateLimit = 50000; // the maximum value of rate limit value - const rateLimit = Number(this.rateLimit.value); - if (rateLimit > maximumRateLimit) { - this.rateLimit.value = maximumRateLimit.toString(); - } - if (rateLimit <= 0) { - this.rateLimit.value = '1'; - } - } - - /** - * Convert the value bytes to GB with decimal point to 1 as a default - * - * @param {number} bytes - * @param {number} decimalPoint decimal point set to 1 as a default - * @return {string} converted value with fixed decimal point - */ - static bytesToGB(bytes, decimalPoint = 1) { - if (!bytes) return bytes; - return (bytes / 10 ** 9).toFixed(decimalPoint); - } - - /** - * Get user id according to configuration - * - * @param {string} userId - * @return {string} - */ - _getUserId(userId = '') { - if (this.isUserInfoMaskEnabled) { - const maskStartIdx = 2; - const maskLength = userId.split('@')[0].length - maskStartIdx; - userId = globalThis.backendaiutils._maskString( - userId, - '*', - maskStartIdx, - maskLength, - ); - } - return userId; - } - - /** - * Get user access key according to configuration - * - * @param {string} accessKey - * @return {string} - */ - _getAccessKey(accessKey = '') { - if (this.isUserInfoMaskEnabled) { - const maskStartIdx = 4; - const maskLength = accessKey.length - maskStartIdx; - accessKey = globalThis.backendaiutils._maskString( - accessKey, - '*', - maskStartIdx, - maskLength, - ); - } - return accessKey; - } - - render() { - // language=HTML - return html` -
- - - - - - - - - - - -
- - ${_t('dialog.title.LetsDouble-Check')} -
-

- You are deleting the credentials of user - ${this.deleteKeyPairUserName} - . -

-

${_t('dialog.ask.DoYouWantToProceed')}

-
-
- - -
-
- - ${_t('credential.KeypairDetail')} -
- ${this.keypairInfo.is_admin - ? html` - - ` - : html``} - -
-
-
-
-

${_t('credential.Information')}

-
- -
${_t('credential.UserID')}
-
${this.keypairInfo.user_id}
-
- -
${_t('general.AccessKey')}
-
${this.keypairInfo.access_key}
-
- -
${_t('general.SecretKey')}
-
${this.keypairInfo.secret_key}
-
- -
${_t('credential.Created')}
-
${this.keypairInfo.created_at}
-
- -
${_t('credential.Lastused')}
-
${this.keypairInfo.last_used}
-
-
-
-
-

${_t('credential.Allocation')}

-
- -
${_t('credential.ResourcePolicy')}
-
${this.keypairInfo.resource_policy}
-
- -
- ${_t('credential.NumberOfQueries')} -
-
${this.keypairInfo.num_queries}
-
- -
- ${_t('credential.ConcurrentSessions')} -
-
- ${this.keypairInfo.concurrency_used} - ${_t('credential.active')} / - ${this.keypairInfo.concurrency_used} - ${_t('credential.concurrentsessions')}. -
-
- -
${_t('credential.RateLimit')}
-
- ${this.keypairInfo.rate_limit} - ${_t('credential.for900seconds')}. -
-
-
-
-
-
-
- - - ${_t('credential.ModifyKeypairResourcePolicy')} - - -
-
- - ${Object.keys(this.resourcePolicy).map( - (rp) => html` - - ${this.resourcePolicy[rp].name} - - `, - )} - -
-
- -
-
-
- -
-
- - ${_t('dialog.title.LetsDouble-Check')} -
-

${_t('credential.WarningLessRateLimit')}

-

${_t('dialog.ask.DoYouWantToProceed')}

-
-
- - -
-
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'backend-ai-credential-list': BackendAICredentialList; - } -} diff --git a/src/components/backend-ai-credential-view.ts b/src/components/backend-ai-credential-view.ts deleted file mode 100644 index e515bb8b12..0000000000 --- a/src/components/backend-ai-credential-view.ts +++ /dev/null @@ -1,902 +0,0 @@ -/** - * Backend.AI-credential-view - */ -import JsonToCsv from '../lib/json_to_csv'; -import { - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, -} from '../plastics/layout/iron-flex-layout-classes'; -import BackendAiCommonUtils from './backend-ai-common-utils'; -import './backend-ai-credential-list'; -import './backend-ai-dialog'; -import { BackendAiStyles } from './backend-ai-general-styles'; -import './backend-ai-multi-select'; -import { BackendAIPage } from './backend-ai-page'; -import { default as PainKiller } from './backend-ai-painkiller'; -import './backend-ai-user-list'; -import './lablup-activity-panel'; -import './lablup-expansion'; -import '@material/mwc-button'; -import '@material/mwc-checkbox'; -import '@material/mwc-formfield'; -import '@material/mwc-icon-button'; -import '@material/mwc-list'; -import { Menu } from '@material/mwc-menu'; -import { Select } from '@material/mwc-select'; -import '@material/mwc-tab'; -import '@material/mwc-tab-bar'; -import { TextField } from '@material/mwc-textfield'; -import { css, CSSResultGroup, html } from 'lit'; -import { get as _text, translate as _t } from 'lit-translate'; -import { customElement, property, query } from 'lit/decorators.js'; - -/* FIXME: - * This type definition is a workaround for resolving both Type error and Importing error. - */ -type BackendAICredentialList = - HTMLElementTagNameMap['backend-ai-credential-list']; -type BackendAIDialog = HTMLElementTagNameMap['backend-ai-dialog']; - -/** - Backend.AI Credential view page - - Example: - - - ... content ... - - -@group Backend.AI Web UI - */ -@customElement('backend-ai-credential-view') -export default class BackendAICredentialView extends BackendAIPage { - @property({ type: Object }) vfolder_max_limit = {}; - @property({ type: Array }) rate_metric = [ - 1000, 2000, 3000, 4000, 5000, 10000, 50000, - ]; - @property({ type: Object }) resource_policies = Object(); - @property({ type: Array }) resource_policy_names; - @property({ type: Boolean }) isAdmin = false; - @property({ type: Boolean }) isSuperAdmin = false; - @property({ type: String }) _status = 'inactive'; - @property({ type: String }) new_access_key = ''; - @property({ type: String }) new_secret_key = ''; - @property({ type: String }) _activeTab = 'users'; - @property({ type: Object }) notification = Object(); - @property({ type: String }) _defaultFileName = ''; - @property({ type: Boolean }) enableSessionLifetime = false; - @property({ type: String }) activeUserInnerTab = 'active'; - @property({ type: String }) activeCredentialInnerTab = 'active'; - @property({ type: Boolean }) openUserSettingModal = false; - @query('#active-credential-list') - activeCredentialList!: BackendAICredentialList; - @query('#inactive-credential-list') - inactiveCredentialList!: BackendAICredentialList; - @query('#rate-limit') rateLimit!: Select; - @query('#resource-policy') resourcePolicy!: Select; - @query('#id_user_email') userEmailInput!: TextField; - @query('#id_new_user_id') userIdInput!: TextField; - @query('#id_user_confirm') userPasswordConfirmInput!: TextField; - @query('#id_user_name') userNameInput!: TextField; - @query('#id_user_password') userPasswordInput!: TextField; - @query('#new-keypair-dialog') newKeypairDialog!: BackendAIDialog; - @query('#new-user-dialog') newUserDialog!: BackendAIDialog; - @query('#export-to-csv') exportToCsvDialog!: BackendAIDialog; - @query('#export-file-name') exportFileNameInput!: TextField; - - constructor() { - super(); - this.resource_policy_names = []; - } - - static get styles(): CSSResultGroup { - return [ - BackendAiStyles, - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, - // language=CSS - css` - #new-keypair-dialog { - min-width: 350px; - height: 100%; - } - - div.card > h4 { - margin-bottom: 0px; - background-color: var(--token-colorBgContainer); - } - - div.card h3 { - padding-top: 0; - padding-right: 15px; - padding-bottom: 0; - } - - div.card div.card { - margin: 0; - padding: 0; - --card-elevation: 0; - } - - div.sessions-section { - width: 167px; - margin-bottom: 10px; - } - - #user-lists > h4, - #credential-lists > h4 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - - mwc-tab-bar.sub-bar mwc-tab { - --mdc-tab-height: 46px; - --mdc-text-transform: none; - } - - mwc-list-item { - height: auto; - font-size: 12px; - --mdc-theme-primary: var(--general-sidebar-color); - } - - mwc-checkbox { - margin-left: 0; - --mdc-icon-size: 14px; - --mdc-checkbox-ripple-size: 20px; - --mdc-checkbox-state-layer-size: 14px; - } - - mwc-formfield { - font-size: 8px; - --mdc-typography-body2-font-size: 10px; - } - - mwc-textfield { - width: 100%; - --mdc-text-field-fill-color: transparent; - --mdc-theme-primary: var(--general-textfield-selected-color); - --mdc-typography-font-family: var(--token-fontFamily); - } - - mwc-textfield#export-file-name { - margin-bottom: 10px; - } - - mwc-textfield#id_user_name { - margin-bottom: 18px; - } - - mwc-menu { - --mdc-menu-item-height: auto; - } - - mwc-menu#dropdown-menu { - position: relative; - left: -10px; - top: 50px; - } - - mwc-icon-button { - --mdc-icon-size: 20px; - color: var(--paper-grey-700); - } - - mwc-icon-button#dropdown-menu-button { - margin-left: 10px; - } - - backend-ai-dialog { - --component-min-width: 350px; - --component-max-width: 390px; - } - - backend-ai-dialog h4 { - font-size: 14px; - padding: 5px 15px 5px 12px; - margin: 0 0 10px 0; - display: block; - height: 20px; - border-bottom: 1px solid var(--token-colorBorder, #ccc); - } - - div.popup-right-margin { - margin-right: 5px; - } - div.popup-left-margin { - margin-left: 5px; - } - div.popup-both-margin { - margin-left: 5px; - margin-right: 5px; - } - - @media screen and (max-width: 805px) { - mwc-tab, - mwc-button { - --mdc-typography-button-font-size: 10px; - } - } - `, - ]; - } - - firstUpdated() { - this.notification = globalThis.lablupNotification; - document.addEventListener( - 'backend-ai-credential-refresh', - () => { - this.activeCredentialList.refresh(); - this.inactiveCredentialList.refresh(); - }, - true, - ); - - this._addInputValidator(this.userIdInput); - } - - /** - * If admin comes, prepare the user list page. - */ - async _preparePage() { - if (globalThis.backendaiclient.is_admin !== true) { - this.disablePage(); - } else { - this.isAdmin = true; - if (globalThis.backendaiclient.is_superadmin === true) { - this.isSuperAdmin = true; - } - } - this._activeTab = 'credential-lists'; - this.vfolder_max_limit['value'] = 10; - this._defaultFileName = this._getDefaultCSVFileName(); - await this._runAction(); - } - - /** - * Change resource policy list's active state and user list's active state. - * - * @param {Boolean} active - */ - async _viewStateChanged(active) { - await this.updateComplete; - if (active === false) { - this._status = 'inactive'; - return; - } - this._status = 'active'; - if ( - typeof globalThis.backendaiclient === 'undefined' || - globalThis.backendaiclient === null || - globalThis.backendaiclient.ready === false - ) { - document.addEventListener('backend-ai-connected', () => { - this.enableSessionLifetime = - globalThis.backendaiclient.supports('session-lifetime'); - this._preparePage(); - }); - } else { - // already connected - this.enableSessionLifetime = - globalThis.backendaiclient.supports('session-lifetime'); - this._preparePage(); - } - } - - /** - * Launch a keypair dialog. - */ - async _launchKeyPairDialog() { - await this._getResourcePolicies(); - this.newKeypairDialog.show(); - - // initialize user_id - this.userIdInput.value = ''; - } - - /** - * Launch an user add dialog. - */ - _launchUserAddDialog() { - this.newUserDialog.show(); - } - - /** - * Open react user create modal. - * UserSettingModal.tsx - */ - _openUserCreateModal() { - this.openUserSettingModal = true; - } - - /** - * Get resource policies from backend client. - */ - async _getResourcePolicies() { - const fields = [ - 'name', - 'default_for_unspecified', - 'total_resource_slots', - 'max_concurrent_sessions', - 'max_containers_per_session', - ]; - if (this.enableSessionLifetime) { - fields.push('max_session_lifetime'); - } - return globalThis.backendaiclient.resourcePolicy - .get(null, fields) - .then((response) => { - const policies = globalThis.backendaiclient.utils.gqlToObject( - response.keypair_resource_policies, - 'name', - ); - const policyNames = globalThis.backendaiclient.utils.gqlToList( - response.keypair_resource_policies, - 'name', - ); - this.resource_policies = policies; - this.resource_policy_names = policyNames; - this.resourcePolicy.layout(true).then(() => { - this.resourcePolicy.select(0); - }); - this.rateLimit.layout(true).then(() => { - this.rateLimit.select(0); - }); - }); - } - - /** - * Create a keypair to the user. - */ - _addKeyPair() { - const is_active = true; - const is_admin = false; - let user_id = ''; - - if (!this.userIdInput.checkValidity()) { - return; - } else { - user_id = this.userIdInput.value; - } - /* deprecate empty user_id regarded as superadmin email */ - // user_id = globalThis.backendaiclient.email; - - /* access_key and secret_key is auto-generated by the manager */ - /* - let access_key = this.shadowRoot.querySelector('#id_new_user_id').value; - let secret_key = this.shadowRoot.querySelector('#id_new_secret_key').value; - globalThis.backendaiclient.keypair.add(user_id, is_active, is_admin, - resource_policy, rate_limit, access_key, secret_key).then(response => { - */ - - const resource_policy = this.resourcePolicy.value; - const rate_limit = parseInt(this.rateLimit.value); - // Read resources - globalThis.backendaiclient.keypair - .add(user_id, is_active, is_admin, resource_policy, rate_limit) - .then((response) => { - if (response.create_keypair.ok) { - this.newKeypairDialog.hide(); - this.notification.text = _text('credential.KeypairCreated'); - this.notification.show(); - this.activeCredentialList.refresh(); - } else if (response.create_keypair.msg) { - const id_requested = response.create_keypair.msg.split(':')[1]; - this.notification.text = - _text('credential.UserNotFound') + id_requested; - this.notification.show(); - } else { - this.notification.text = _text('dialog.ErrorOccurred'); - this.notification.show(); - } - }) - .catch((err) => { - console.log(err); - if (err && err.message) { - this.newKeypairDialog.hide(); - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - }); - } - - /** - * Disable the page. - */ - disablePage() { - const els = this.shadowRoot?.querySelectorAll( - '.admin', - ) as NodeListOf; - for (let x = 0; x < els.length; x++) { - els[x].style.display = 'none'; - } - } - - /** - * Display the tab. - * - * @param {EventTarget} tab - Tab webcomponent - */ - _showTab(tab) { - const els = this.shadowRoot?.querySelectorAll( - '.tab-content', - ) as NodeListOf; - for (let x = 0; x < els.length; x++) { - els[x].style.display = 'none'; - } - this._activeTab = tab.title; - ( - this.shadowRoot?.querySelector('#' + tab.title) as HTMLElement - ).style.display = 'block'; - const tabKeyword = this._activeTab.substring(0, this._activeTab.length - 1); // to remove '-s'. - let innerTab; - // show inner tab(active) after selecting outer tab - switch (this._activeTab) { - case 'credential-lists': - innerTab = this.shadowRoot?.querySelector( - 'mwc-tab[title=' + - this.activeCredentialInnerTab + - '-' + - tabKeyword + - ']', - ); - this._showList(innerTab); - break; - default: - break; - } - } - - /** - * Display the list. - * - * @param {EventTarget} list - List webcomponent - */ - _showList(list) { - const els = this.shadowRoot?.querySelectorAll( - '.list-content', - ) as NodeListOf; - for (let x = 0; x < els.length; x++) { - els[x].style.display = 'none'; - } - ( - this.shadowRoot?.querySelector('#' + list.title) as HTMLElement - ).style.display = 'block'; - const splitTitle = list.title.split('-'); - if (splitTitle[1] == 'user') { - this.activeUserInnerTab = splitTitle[0]; - } else { - this.activeCredentialInnerTab = splitTitle[0]; - } - const event = new CustomEvent('user-list-updated', {}); - this.shadowRoot?.querySelector('#' + list.title)?.dispatchEvent(event); - } - - /** - * Display a export to csv dialog. - */ - _openExportToCsvDialog() { - this._defaultFileName = this._getDefaultCSVFileName(); - this.exportToCsvDialog.show(); - } - - /** - * Export user list and credential lists and resource policy lists to csv. - */ - _exportToCSV() { - if (!this.exportFileNameInput.validity.valid) { - return; - } - let credential_active; - let credential_inactive; - let credential; - switch (this._activeTab) { - case 'credential-lists': - credential_active = this.activeCredentialList.keypairs; - credential_inactive = this.inactiveCredentialList.keypairs; - credential = credential_active.concat(credential_inactive); - credential.map((obj) => { - // filtering unnecessary key - ['is_admin'].forEach((key) => delete obj[key]); - }); - JsonToCsv.exportToCsv(this.exportFileNameInput.value, credential); - break; - } - this.notification.text = _text('session.DownloadingCSVFile'); - this.notification.show(); - this.exportToCsvDialog.hide(); - } - - /** - * Get default csv file name according to local time. - * - * @return {string} - Filename with Date and Time. - */ - _getDefaultCSVFileName() { - const date = new Date().toISOString().substring(0, 10); - const time = new Date().toTimeString().slice(0, 8).replace(/:/gi, '-'); - return date + '_' + time; - } - - /** - * Control a dropdown menu's open state. - * - * @param {Event} e - event from dropdown component. - */ - _toggleDropdown(e) { - const menu = this.shadowRoot?.querySelector('#dropdown-menu') as Menu; - const button = e.target; - menu.anchor = button; - if (!menu.open) { - menu.show(); - } - } - - _validatePassword1() { - this.userPasswordConfirmInput.reportValidity(); - this.userPasswordInput.validityTransform = (newValue, nativeValidity) => { - if (!nativeValidity.valid) { - if (nativeValidity.valueMissing) { - this.userPasswordInput.validationMessage = _text( - 'signup.PasswordInputRequired', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } else { - this.userPasswordInput.validationMessage = _text( - 'signup.PasswordInvalid', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - } else { - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - }; - } - - _validatePassword2() { - this.userPasswordConfirmInput.validityTransform = ( - newValue, - nativeValidity, - ) => { - if (!nativeValidity.valid) { - if (nativeValidity.valueMissing) { - this.userPasswordConfirmInput.validationMessage = _text( - 'signup.PasswordInputRequired', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } else { - this.userPasswordConfirmInput.validationMessage = _text( - 'signup.PasswordInvalid', - ); - return { - valid: nativeValidity.valid, - customError: !nativeValidity.valid, - }; - } - } else { - // custom validation for password input match - const isMatched = - this.userPasswordInput.value === this.userPasswordConfirmInput.value; - if (!isMatched) { - this.userPasswordConfirmInput.validationMessage = _text( - 'signup.PasswordNotMatched', - ); - } - return { - valid: isMatched, - customError: !isMatched, - }; - } - }; - } - - _validatePassword() { - this._validatePassword1(); - this._validatePassword2(); - } - - _togglePasswordVisibility(element) { - const isVisible = element.__on; - const password = element.closest('div').querySelector('mwc-textfield'); - isVisible - ? password.setAttribute('type', 'text') - : password.setAttribute('type', 'password'); - } - - static gBToBytes(value = 0) { - const gigabyte = Math.pow(10, 9); - return Math.round(gigabyte * value); - } - - async _runAction() { - if (location.search.includes('action')) { - if (location.search.includes('add')) { - await this._launchKeyPairDialog(); - } - this._showTab( - this.shadowRoot?.querySelector('mwc-tab[title=credential-lists]'), - ); - this.shadowRoot - ?.querySelector('mwc-tab-bar.main-bar') - ?.setAttribute('activeindex', '1'); - } - } - - render() { - // language=HTML - return html` - - -
-

- -

- -
-

- - - - -
- ${this.isAdmin - ? html` - - - ` - : html``} - -

- - -
-
-
- - ${_t('credential.AddCredential')} -
-
- - - - ${this.resource_policy_names.map( - (item) => html` - ${item} - `, - )} - - - ${this.rate_metric.map( - (item) => html` - ${item} - `, - )} - - -
-
-
- -
-
- - ${_t('credential.CreateUser')} -
- - -
- - -
-
- - -
-
-
- - - ${_t('credential.ExportCSVFile')} (${this._activeTab}) - - - -
- -
-
- `; - } -} -declare global { - interface HTMLElementTagNameMap { - 'backend-ai-credential-view': BackendAICredentialView; - } -} diff --git a/src/components/backend-ai-user-list.ts b/src/components/backend-ai-user-list.ts deleted file mode 100644 index d74f409da4..0000000000 --- a/src/components/backend-ai-user-list.ts +++ /dev/null @@ -1,753 +0,0 @@ -/** - @license - Copyright (c) 2015-2024 Lablup Inc. All rights reserved. - */ -import '../plastics/lablup-shields/lablup-shields'; -import { - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, -} from '../plastics/layout/iron-flex-layout-classes'; -import './backend-ai-dialog'; -import { BackendAiStyles } from './backend-ai-general-styles'; -import './backend-ai-list-status'; -import BackendAIListStatus, { StatusCondition } from './backend-ai-list-status'; -import { BackendAIPage } from './backend-ai-page'; -import { default as PainKiller } from './backend-ai-painkiller'; -import './lablup-grid-sort-filter-column'; -import '@material/mwc-button'; -import '@material/mwc-icon-button'; -import { Select } from '@material/mwc-select'; -import '@material/mwc-switch'; -import '@material/mwc-textarea'; -import '@material/mwc-textfield'; -import '@vaadin/grid/vaadin-grid'; -import '@vaadin/grid/vaadin-grid-filter-column'; -import '@vaadin/grid/vaadin-grid-sort-column'; -import '@vaadin/icons/vaadin-icons'; -import '@vaadin/item/vaadin-item'; -import { css, CSSResultGroup, html, render } from 'lit'; -import { get as _text, translate as _t } from 'lit-translate'; -import { customElement, property, query } from 'lit/decorators.js'; - -/* FIXME: - * This type definition is a workaround for resolving both Type error and Importing error. - */ -type TextArea = HTMLElementTagNameMap['mwc-textarea']; -type TextField = HTMLElementTagNameMap['mwc-textfield']; -type VaadinGrid = HTMLElementTagNameMap['vaadin-grid']; -type LablupLoadingSpinner = HTMLElementTagNameMap['lablup-loading-spinner']; -type BackendAIDialog = HTMLElementTagNameMap['backend-ai-dialog']; - -/** - Backend AI User List - - `backend-ai-user-list` is list of user details. - Through this, user information can be read or modified, and the user can be logged out. - - Example: - - - ... - - -@group Backend.AI Web UI - @element backend-ai-user-list - */ - -@customElement('backend-ai-user-list') -export default class BackendAIUserList extends BackendAIPage { - @property({ type: Boolean }) isAdmin = false; - @property({ type: Boolean }) editMode = false; - @property({ type: Array }) users = []; - @property({ type: Object }) userInfo = Object(); - @property({ type: Array }) userInfoGroups = []; - @property({ type: String }) userEmail = ''; - @property({ type: Boolean }) openUserInfoModal = false; - @property({ type: Boolean }) openUserSettingModal = false; - @property({ type: String }) condition = ''; - @property({ type: Object }) _boundControlRenderer = - this.controlRenderer.bind(this); - @property({ type: Object }) _userIdRenderer = this.userIdRenderer.bind(this); - @property({ type: Object }) _userNameRenderer = - this.userNameRenderer.bind(this); - @property({ type: Object }) _userStatusRenderer = - this.userStatusRenderer.bind(this); - @property({ type: Object }) _totpActivatedRenderer = - this.totpActivatedRenderer.bind(this); - @property({ type: Object }) keypairs; - @property({ type: String }) signoutUserName = ''; - @property({ type: Object }) notification = Object(); - @property({ type: String }) listCondition: StatusCondition = 'loading'; - @property({ type: Number }) _totalUserCount = 0; - @property({ type: Boolean }) isUserInfoMaskEnabled = false; - @property({ type: Boolean }) totpSupported = false; - @property({ type: Boolean }) totpActivated = false; - @property({ type: Boolean }) supportMainAccessKey = false; - @property({ type: Object }) userStatus = { - active: 'Active', - inactive: 'Inactive', - 'before-verification': 'Before Verification', - deleted: 'Deleted', - }; - @query('#user-grid') userGrid!: VaadinGrid; - @query('#loading-spinner') spinner!: LablupLoadingSpinner; - @query('#list-status') private _listStatus!: BackendAIListStatus; - @query('#password') passwordInput!: TextField; - @query('#confirm') confirmInput!: TextField; - @query('#username') usernameInput!: TextField; - @query('#full_name') fullNameInput!: TextField; - @query('#description') descriptionInput!: TextArea; - @query('#status') statusSelect!: Select; - @query('#signout-user-dialog') signoutUserDialog!: BackendAIDialog; - - constructor() { - super(); - } - - static get styles(): CSSResultGroup | undefined { - return [ - BackendAiStyles, - IronFlex, - IronFlexAlignment, - IronFlexFactors, - IronPositioning, - // language=CSS - css` - vaadin-grid { - border: 0; - font-size: 14px; - height: calc(100vh - 229px); - } - - backend-ai-dialog h4 { - font-size: 14px; - padding: 5px 15px 5px 12px; - margin: 0 0 10px 0; - display: block; - height: 20px; - border-bottom: 1px solid var(--token-colorBorder, #ccc); - } - - vaadin-item { - font-size: 13px; - font-weight: 100; - } - - div.indicator, - span.indicator { - font-size: 9px; - margin-right: 5px; - } - - div.configuration { - width: 70px !important; - } - - div.password-area { - width: 100%; - max-width: 322px; - } - - mwc-textfield.display-textfield { - --mdc-text-field-disabled-ink-color: var(--token-colorText); - } - - backend-ai-dialog li { - font-family: var(--token-fontFamily); - font-size: 16px; - } - - mwc-button, - mwc-button[unelevated], - mwc-button[outlined] { - background-image: none; - --mdc-theme-primary: var(--general-button-background-color); - --mdc-theme-on-primary: var(--general-button-color); - --mdc-typography-font-family: var(--token-fontFamily); - } - - mwc-select.full-width { - width: 100%; - /* Need to be set when fixedMenuPosition attribute is enabled */ - --mdc-menu-max-width: 330px; - --mdc-menu-min-width: 330px; - } - - mwc-textfield, - mwc-textarea { - width: 100%; - --mdc-typography-font-family: var(--token-fontFamily); - --mdc-typography-textfield-font-size: 14px; - --mdc-typography-textarea-font-size: 14px; - --mdc-text-field-fill-color: transparent; - --mdc-theme-primary: var(--general-textfield-selected-color); - } - - p.label { - font-size: 16px; - font-family: var(--token-fontFamily); - color: var(--general-sidebar-color); - width: 270px; - } - - mwc-icon.totp { - --mdc-icon-size: 24px; - } - `, - ]; - } - - firstUpdated() { - this.notification = globalThis.lablupNotification; - this.addEventListener('user-list-updated', () => { - this.refresh(); - }); - } - - /** - * If active is true, change view state - * - * @param {Boolean} active - boolean value that determines whether view state is changed or not - * */ - async _viewStateChanged(active) { - await this.updateComplete; - if (active === false) { - return; - } - // If disconnected - if ( - typeof globalThis.backendaiclient === 'undefined' || - globalThis.backendaiclient === null || - globalThis.backendaiclient.ready === false - ) { - document.addEventListener( - 'backend-ai-connected', - async () => { - this.totpSupported = - globalThis.backendaiclient?.supports('2FA') && - (await globalThis.backendaiclient?.isManagerSupportingTOTP()); - this.supportMainAccessKey = - globalThis.backendaiclient?.supports('main-access-key'); - this._refreshUserData(); - this.isAdmin = globalThis.backendaiclient.is_admin; - this.isUserInfoMaskEnabled = - globalThis.backendaiclient._config.maskUserInfo; - }, - true, - ); - } else { - // already connected - this.totpSupported = - globalThis.backendaiclient?.supports('2FA') && - (await globalThis.backendaiclient?.isManagerSupportingTOTP()); - this.supportMainAccessKey = - globalThis.backendaiclient?.supports('main-access-key'); - this._refreshUserData(); - this.isAdmin = globalThis.backendaiclient.is_admin; - this.isUserInfoMaskEnabled = - globalThis.backendaiclient._config.maskUserInfo; - } - } - - _refreshUserData() { - let is_active = true; - switch (this.condition) { - case 'active': - is_active = true; - break; - default: - is_active = false; - } - this.listCondition = 'loading'; - this._listStatus?.show(); - const fields = [ - 'email', - 'username', - 'need_password_change', - 'full_name', - 'description', - 'is_active', - 'domain_name', - 'role', - 'groups {id name}', - 'status', - 'main_access_key', - ]; - if (this.totpSupported) { - fields.push('totp_activated'); - } - return globalThis.backendaiclient.user - .list(is_active, fields) - .then((response) => { - const users = response.users; - // Object.keys(users).map((objectKey, index) => { - // var user = users[objectKey]; - // Blank for the next impl. - // }); - this.users = users; - if (this.users.length == 0) { - this.listCondition = 'no-data'; - } else { - this._listStatus?.hide(); - } - // setTimeout(() => { this._refreshKeyData(status) }, 5000); - }) - .catch((err) => { - this._listStatus?.hide(); - console.log(err); - if (err && err.message) { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - this.notification.show(true, err); - } - }); - } - - async _openUserSettingModal(e) { - const controls = e.target.closest('#controls'); - this.userEmail = controls['user-id']; - this.openUserSettingModal = true; - } - - async _openUserInfoModal(e) { - const controls = e.target.closest('#controls'); - this.userEmail = controls['user-id']; - this.openUserInfoModal = true; - } - - _signoutUserDialog(e) { - const controls = e.target.closest('#controls'); - const user_id = controls['user-id']; - this.signoutUserName = user_id; - this.signoutUserDialog.show(); - } - - _signoutUser() { - globalThis.backendaiclient.user - .delete(this.signoutUserName) - .then((response) => { - this.notification.text = _text( - 'credential.SignoutSeccessfullyFinished', - ); - this.notification.show(); - this._refreshUserData(); - this.signoutUserDialog.hide(); - }) - .catch((err) => { - // Signout failed - console.log(err); - if (typeof err.message !== 'undefined') { - this.notification.text = PainKiller.relieve(err.title); - this.notification.detail = err.message; - } else { - this.notification.text = PainKiller.relieve( - 'Signout failed. Check your permission and try again.', - ); - } - this.notification.show(); - }); - } - - async _getUserData(user_id) { - const fields = [ - 'email', - 'username', - 'need_password_change', - 'full_name', - 'description', - 'status', - 'domain_name', - 'role', - 'groups {id name}', - 'main_access_key', - ]; - if (this.totpSupported) { - fields.push('totp_activated'); - } - return globalThis.backendaiclient.user.get(user_id, fields); - } - - refresh() { - this._refreshUserData(); - // update current grid to new data - this.userGrid.clearCache(); - } - - _isActive() { - return this.condition === 'active'; - } - - /** - * Return elapsed time - * - * @param {Date} start - * @param {Date} end - * @return {number} Days since start till end - * */ - _elapsed(start, end) { - const startDate = new Date(start); - let endDate: Date; - if (this.condition == 'active') { - endDate = new Date(); - } else { - endDate = new Date(); - } - const seconds = Math.floor( - (endDate.getTime() - startDate.getTime()) / 1000, - ); - const days = Math.floor(seconds / 86400); - return days; - } - - /** - * Date to UTC string - * - * @param {Date} d - date - * @return {string} UTC string - * */ - _humanReadableTime(d) { - return new Date(d).toUTCString(); - } - - /** - * If value includes unlimited contents, mark as unlimited. - * - * @param {string} value - string value - * @return {string} ∞ when value contains -, 0, 'Unlimited', Infinity, 'Infinity' - */ - _markIfUnlimited(value) { - if (['-', 0, 'Unlimited', Infinity, 'Infinity'].includes(value)) { - return '∞'; - } else { - return value; - } - } - - /** - * Get user id according to configuration - * - * @param {string} userId - * @return {string} - */ - _getUserId(userId = '') { - if (userId && this.isUserInfoMaskEnabled) { - const maskStartIdx = 2; - const maskLength = userId.split('@')[0].length - maskStartIdx; - userId = globalThis.backendaiutils._maskString( - userId, - '*', - maskStartIdx, - maskLength, - ); - } - return userId; - } - - _getUsername(username = '') { - if (username && this.isUserInfoMaskEnabled) { - const maskStartIdx = 2; - const maskLength = username.length - maskStartIdx; - username = globalThis.backendaiutils._maskString( - username, - '*', - maskStartIdx, - maskLength, - ); - } - return username; - } - - async _setTotpActivated() { - if (this.totpSupported) { - const userInfo = await globalThis.backendaiclient.user.get( - globalThis.backendaiclient.email, - ['totp_activated'], - ); - this.totpActivated = userInfo.user.totp_activated; - } - } - - /** - * Render index to root element. - * - * @param {Element} root - the row details content DOM element - * @param {Element} column - the column element that controls the state of the host element - * @param {Object} rowData - the object with the properties related with the rendered item - * */ - _indexRenderer(root, column, rowData) { - const idx = rowData.index + 1; - render( - html` -
${idx}
- `, - root, - ); - } - - /** - * Control rendering - showUserDetail, editUserDetail, signoutUserDialog. - * - * @param {Element} root - the row details content DOM element - * @param {Element} column - the column element that controls the state of the host element - * @param {Object} rowData - the object with the properties related with the rendered item - */ - controlRenderer(root, column?, rowData?) { - render( - html` -
- - - ${globalThis.backendaiclient.is_superadmin && this._isActive() - ? html` - - ` - : html``} -
- `, - root, - ); - } - - /** - * Render UserId according to configuration - * - * @param {Element} root - the row details content DOM element - * @param {Element} column - the column element that controls the state of the host element - * @param {Object} rowData - the object with the properties related with the rendered item - */ - userIdRenderer(root, column?, rowData?) { - render( - html` - ${this._getUserId(rowData.item.email)} - `, - root, - ); - } - - /** - * Render Username according to configuration - * - * @param {Element} root - the row details content DOM element - * @param {Element} column - the column element that controls the state of the host element - * @param {Object} rowData - the object with the properties related with the rendered item - */ - userNameRenderer(root, column?, rowData?) { - render( - html` - ${this._getUsername(rowData.item.username)} - `, - root, - ); - } - - /** - * Render current status of user - * - active - * - inactive - * - before-verification - * - deleted - * - * @param {Element} root - * @param {Element} column - * @param {Object} rowData - */ - userStatusRenderer(root, column?, rowData?) { - const color = rowData.item.status === 'active' ? 'green' : 'lightgrey'; - render( - html` - - `, - root, - ); - } - - /** - * Render current status of user - * - active - * - inactive - * - before-verification - * - deleted - * - * @param {Element} root - * @param {Element} column - * @param {Object} rowData - */ - totpActivatedRenderer(root, column?, rowData?) { - render( - html` -
- ${rowData.item?.totp_activated - ? html` - check_circle - ` - : html` - block - `} -
- `, - root, - ); - } - - render() { - // language=HTML - return html` - - -
- - - - - ${this.totpSupported - ? html` - - ` - : html``} - ${this.condition !== 'active' - ? html` - - ` - : html``} - ${this.supportMainAccessKey - ? html` - - ` - : html``} - - - -
- - ${_t('dialog.title.LetsDouble-Check')} -
-

- ${_t('credential.InactivateTheFollowingUsers')} - - ${this.signoutUserName} - - . -

-

${_t('dialog.ask.DoYouWantToProceed')}

-
-
- - -
-
- ${this.openUserInfoModal - ? html` - - ` - : html``} - ${this.openUserSettingModal - ? html` - - ` - : html``} - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'backend-ai-user-list': BackendAIUserList; - } -}