You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/app/client/ui/ApiKey.ts

151 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { basicButton, textButton } from 'app/client/ui2018/buttons';
import { theme, vars } from 'app/client/ui2018/cssVars';
import { icon } from 'app/client/ui2018/icons';
import { confirmModal } from 'app/client/ui2018/modals';
import { Disposable, dom, IDomArgs, makeTestId, Observable, observable, styled } from 'grainjs';
interface IWidgetOptions {
apiKey: Observable<string>;
onDelete: () => Promise<void>;
onCreate: () => Promise<void>;
anonymous?: boolean; // Configure appearance and available options for anonymous use.
// When anonymous, no modifications are permitted to profile information.
// TODO: add browser test for this option.
inputArgs?: IDomArgs<HTMLInputElement>;
}
const testId = makeTestId('test-apikey-');
/**
* ApiKey component shows an api key with controls to change it. Expects `options.apiKey` the api
* key and shows it if value is truthy along with a 'Delete' button that triggers the
* `options.onDelete` callback. When `options.apiKey` is falsy, hides it and show a 'Create' button
* that triggers the `options.onCreate` callback. It is the responsibility of the caller to update
* the `options.apiKey` to its new value.
*/
export class ApiKey extends Disposable {
private _apiKey: Observable<string>;
private _onDeleteCB: () => Promise<void>;
private _onCreateCB: () => Promise<void>;
private _anonymous: boolean;
private _inputArgs: IDomArgs<HTMLInputElement>;
private _loading = observable(false);
private _isHidden: Observable<boolean> = Observable.create(this, true);
constructor(options: IWidgetOptions) {
super();
this._apiKey = options.apiKey;
this._onDeleteCB = options.onDelete;
this._onCreateCB = options.onCreate;
this._anonymous = Boolean(options.anonymous);
this._inputArgs = options.inputArgs ?? [];
}
public buildDom() {
return dom('div', testId('container'), dom.style('position', 'relative'),
dom.maybe(this._apiKey, (apiKey) => dom('div',
cssRow(
cssInput(
{
readonly: true,
value: this._apiKey.get(),
},
dom.attr('type', (use) => use(this._isHidden) ? 'password' : 'text'),
testId('key'),
{title: 'Click to show'},
dom.on('click', (_ev, el) => {
this._isHidden.set(false);
setTimeout(() => el.select(), 0);
}),
dom.on('blur', (ev) => {
// Hide the key when it is no longer selected.
if (ev.target !== document.activeElement) { this._isHidden.set(true); }
}),
this._inputArgs
),
cssTextBtn(
cssTextBtnIcon('Remove'), 'Remove',
dom.on('click', () => this._showRemoveKeyModal()),
testId('delete'),
dom.boolAttr('disabled', (use) => use(this._loading) || this._anonymous)
),
),
description(this._getDescription(), testId('description')),
)),
dom.maybe((use) => !(use(this._apiKey) || this._anonymous), () => [
basicButton('Create', dom.on('click', () => this._onCreate()), testId('create'),
dom.boolAttr('disabled', this._loading)),
description('By generating an API key, you will be able to make API calls '
+ 'for your own account.', testId('description')),
]),
);
}
// Switch the `_loading` flag to `true` and later, once promise resolves, switch it back to
// `false`.
private async _switchLoadingFlag(promise: Promise<any>) {
this._loading.set(true);
try {
await promise;
} finally {
this._loading.set(false);
}
}
private _onDelete(): Promise<void> {
return this._switchLoadingFlag(this._onDeleteCB());
}
private _onCreate(): Promise<void> {
return this._switchLoadingFlag(this._onCreateCB());
}
private _getDescription(): string {
if (!this._anonymous) {
return 'This API key can be used to access your account via the API. '
+ 'Dont share your API key with anyone.';
} else {
return 'This API key can be used to access this account anonymously via the API.';
}
}
private _showRemoveKeyModal(): void {
confirmModal(
`Remove API Key`, 'Remove',
() => this._onDelete(),
`You're about to delete an API key. This will cause all future ` +
`requests using this API key to be rejected. Do you still want to delete?`
);
}
}
const description = styled('div', `
margin-top: 8px;
color: ${theme.lightText};
font-size: ${vars.mediumFontSize};
`);
const cssInput = styled('input', `
background-color: transparent;
color: ${theme.inputFg};
border: 1px solid ${theme.inputBorder};
padding: 4px;
border-radius: 3px;
outline: none;
flex: 1 0 0;
`);
const cssRow = styled('div', `
display: flex;
`);
const cssTextBtn = styled(textButton, `
text-align: left;
width: 90px;
margin-left: 16px;
`);
const cssTextBtnIcon = styled(icon, `
margin: 0 4px 2px 0;
`);