284 lines
9.0 KiB
TypeScript
284 lines
9.0 KiB
TypeScript
import {
|
|
DeesElement,
|
|
html,
|
|
customElement,
|
|
type TemplateResult,
|
|
css,
|
|
state,
|
|
cssManager,
|
|
} from '@design.estate/dees-element';
|
|
import * as appstate from '../../appstate.js';
|
|
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
import { viewHostCss } from '../shared/css.js';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'ops-view-providers': OpsViewProviders;
|
|
}
|
|
}
|
|
|
|
@customElement('ops-view-providers')
|
|
export class OpsViewProviders extends DeesElement {
|
|
@state()
|
|
accessor domainsState: appstate.IDomainsState = appstate.domainsStatePart.getState()!;
|
|
|
|
constructor() {
|
|
super();
|
|
const sub = appstate.domainsStatePart.select().subscribe((newState) => {
|
|
this.domainsState = newState;
|
|
});
|
|
this.rxSubscriptions.push(sub);
|
|
}
|
|
|
|
async connectedCallback() {
|
|
await super.connectedCallback();
|
|
await appstate.domainsStatePart.dispatchAction(appstate.fetchDomainsAndProvidersAction, null);
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
viewHostCss,
|
|
css`
|
|
.providersContainer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
.statusBadge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.statusBadge.ok {
|
|
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
|
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
|
}
|
|
|
|
.statusBadge.error {
|
|
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
|
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
|
}
|
|
|
|
.statusBadge.untested {
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
|
|
color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
const providers = this.domainsState.providers;
|
|
|
|
return html`
|
|
<dees-heading level="3">DNS Providers</dees-heading>
|
|
<div class="providersContainer">
|
|
<dees-table
|
|
.heading1=${'Providers'}
|
|
.heading2=${'External DNS provider accounts (Cloudflare, etc.)'}
|
|
.data=${providers}
|
|
.showColumnFilters=${true}
|
|
.displayFunction=${(p: interfaces.data.IDnsProviderPublic) => ({
|
|
Name: p.name,
|
|
Type: p.type,
|
|
Status: this.renderStatusBadge(p.status),
|
|
'Last Tested': p.lastTestedAt ? new Date(p.lastTestedAt).toLocaleString() : 'never',
|
|
Error: p.lastError || '-',
|
|
})}
|
|
.dataActions=${[
|
|
{
|
|
name: 'Add Provider',
|
|
iconName: 'lucide:plus',
|
|
type: ['header' as const],
|
|
actionFunc: async () => {
|
|
await this.showCreateDialog();
|
|
},
|
|
},
|
|
{
|
|
name: 'Refresh',
|
|
iconName: 'lucide:rotateCw',
|
|
type: ['header' as const],
|
|
actionFunc: async () => {
|
|
await appstate.domainsStatePart.dispatchAction(
|
|
appstate.fetchDomainsAndProvidersAction,
|
|
null,
|
|
);
|
|
},
|
|
},
|
|
{
|
|
name: 'Test Connection',
|
|
iconName: 'lucide:plug',
|
|
type: ['inRow', 'contextmenu'] as any,
|
|
actionFunc: async (actionData: any) => {
|
|
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
await this.testProvider(provider);
|
|
},
|
|
},
|
|
{
|
|
name: 'Edit',
|
|
iconName: 'lucide:pencil',
|
|
type: ['inRow', 'contextmenu'] as any,
|
|
actionFunc: async (actionData: any) => {
|
|
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
await this.showEditDialog(provider);
|
|
},
|
|
},
|
|
{
|
|
name: 'Delete',
|
|
iconName: 'lucide:trash2',
|
|
type: ['inRow', 'contextmenu'] as any,
|
|
actionFunc: async (actionData: any) => {
|
|
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
await this.deleteProvider(provider);
|
|
},
|
|
},
|
|
]}
|
|
></dees-table>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderStatusBadge(status: interfaces.data.TDnsProviderStatus): TemplateResult {
|
|
return html`<span class="statusBadge ${status}">${status}</span>`;
|
|
}
|
|
|
|
private async showCreateDialog() {
|
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
DeesModal.createAndShow({
|
|
heading: 'Add DNS Provider',
|
|
content: html`
|
|
<dees-form>
|
|
<dees-input-text .key=${'name'} .label=${'Provider name'} .required=${true}></dees-input-text>
|
|
<dees-input-text
|
|
.key=${'apiToken'}
|
|
.label=${'Cloudflare API token'}
|
|
.required=${true}
|
|
></dees-input-text>
|
|
</dees-form>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
{
|
|
name: 'Create',
|
|
action: async (modalArg: any) => {
|
|
const form = modalArg.shadowRoot
|
|
?.querySelector('.content')
|
|
?.querySelector('dees-form');
|
|
if (!form) return;
|
|
const data = await form.collectFormData();
|
|
await appstate.domainsStatePart.dispatchAction(appstate.createDnsProviderAction, {
|
|
name: String(data.name),
|
|
type: 'cloudflare',
|
|
credentials: { type: 'cloudflare', apiToken: String(data.apiToken) },
|
|
});
|
|
modalArg.destroy();
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
private async showEditDialog(provider: interfaces.data.IDnsProviderPublic) {
|
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
DeesModal.createAndShow({
|
|
heading: `Edit Provider: ${provider.name}`,
|
|
content: html`
|
|
<dees-form>
|
|
<dees-input-text .key=${'name'} .label=${'Provider name'} .value=${provider.name}></dees-input-text>
|
|
<dees-input-text
|
|
.key=${'apiToken'}
|
|
.label=${'New API token (leave blank to keep current)'}
|
|
></dees-input-text>
|
|
</dees-form>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
{
|
|
name: 'Save',
|
|
action: async (modalArg: any) => {
|
|
const form = modalArg.shadowRoot
|
|
?.querySelector('.content')
|
|
?.querySelector('dees-form');
|
|
if (!form) return;
|
|
const data = await form.collectFormData();
|
|
const apiToken = data.apiToken ? String(data.apiToken) : '';
|
|
await appstate.domainsStatePart.dispatchAction(appstate.updateDnsProviderAction, {
|
|
id: provider.id,
|
|
name: String(data.name),
|
|
credentials: apiToken
|
|
? { type: 'cloudflare', apiToken }
|
|
: undefined,
|
|
});
|
|
modalArg.destroy();
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
private async testProvider(provider: interfaces.data.IDnsProviderPublic) {
|
|
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
await appstate.domainsStatePart.dispatchAction(appstate.testDnsProviderAction, {
|
|
id: provider.id,
|
|
});
|
|
const updated = appstate.domainsStatePart
|
|
.getState()!
|
|
.providers.find((p) => p.id === provider.id);
|
|
if (updated?.status === 'ok') {
|
|
DeesToast.show({
|
|
message: `${provider.name}: connection OK`,
|
|
type: 'success',
|
|
duration: 3000,
|
|
});
|
|
} else {
|
|
DeesToast.show({
|
|
message: `${provider.name}: ${updated?.lastError || 'connection failed'}`,
|
|
type: 'error',
|
|
duration: 4000,
|
|
});
|
|
}
|
|
}
|
|
|
|
private async deleteProvider(provider: interfaces.data.IDnsProviderPublic) {
|
|
const linkedDomains = this.domainsState.domains.filter((d) => d.providerId === provider.id);
|
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
|
|
const doDelete = async (force: boolean) => {
|
|
await appstate.domainsStatePart.dispatchAction(appstate.deleteDnsProviderAction, {
|
|
id: provider.id,
|
|
force,
|
|
});
|
|
};
|
|
|
|
if (linkedDomains.length > 0) {
|
|
DeesModal.createAndShow({
|
|
heading: `Provider in use`,
|
|
content: html`
|
|
<p>
|
|
Provider <strong>${provider.name}</strong> is referenced by ${linkedDomains.length}
|
|
domain(s). Deleting will also remove the imported domain(s) and their cached
|
|
records (the records at ${provider.type} are NOT touched).
|
|
</p>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
{
|
|
name: 'Force Delete',
|
|
action: async (modalArg: any) => {
|
|
await doDelete(true);
|
|
modalArg.destroy();
|
|
},
|
|
},
|
|
],
|
|
});
|
|
} else {
|
|
await doDelete(false);
|
|
}
|
|
}
|
|
}
|