Merge branch 'main' into feature/templates-scaffold
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import type { ManifestElement } from './models';
|
||||
|
||||
export interface ManifestHealthCheck extends ManifestElement {
|
||||
type: 'healthCheck';
|
||||
meta: MetaHealthCheck;
|
||||
}
|
||||
|
||||
export interface MetaHealthCheck {
|
||||
label: string;
|
||||
api: any;
|
||||
}
|
||||
|
||||
export interface HealthCheck {
|
||||
alias: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import type { ManifestPackageView } from './package-view.models';
|
||||
import type { ManifestExternalLoginProvider } from './external-login-provider.models';
|
||||
import type { ManifestCollectionBulkAction } from './collection-bulk-action.models';
|
||||
import type { ManifestCollectionView } from './collection-view.models';
|
||||
import type { ManifestHealthCheck } from './health-check.models';
|
||||
import type { ManifestSidebarMenuItem } from './sidebar-menu-item.models';
|
||||
|
||||
export * from './header-app.models';
|
||||
@@ -36,6 +37,7 @@ export * from './package-view.models';
|
||||
export * from './external-login-provider.models';
|
||||
export * from './collection-bulk-action.models';
|
||||
export * from './collection-view.models';
|
||||
export * from './health-check.models';
|
||||
export * from './sidebar-menu-item.models';
|
||||
|
||||
export type ManifestTypes =
|
||||
@@ -60,6 +62,7 @@ export type ManifestTypes =
|
||||
| ManifestEntrypoint
|
||||
| ManifestCollectionBulkAction
|
||||
| ManifestCollectionView
|
||||
| ManifestHealthCheck
|
||||
| ManifestSidebarMenuItem;
|
||||
|
||||
export type ManifestStandardTypes = ManifestTypes['type'];
|
||||
|
||||
@@ -1,17 +1,330 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, state, query, property } from 'lit/decorators.js';
|
||||
import { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-ui/uui';
|
||||
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { RedirectManagementResource, RedirectStatus, RedirectUrl } from '@umbraco-cms/backend-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
|
||||
@customElement('umb-dashboard-redirect-management')
|
||||
export class UmbDashboardRedirectManagementElement extends LitElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
export class UmbDashboardRedirectManagementElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.actions uui-icon {
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
uui-table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
uui-table-head-cell:nth-child(2*n) {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
uui-table-head-cell:last-child,
|
||||
uui-table-cell:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
uui-table uui-icon {
|
||||
vertical-align: sub;
|
||||
}
|
||||
uui-pagination {
|
||||
display: inline-block;
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
.trackerDisabled {
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.trackerDisabled::after {
|
||||
content: '';
|
||||
background-color: rgba(250, 250, 250, 0.7);
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--uui-color-interactive);
|
||||
}
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: var(--uui-color-interactive-emphasis);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: Number, attribute: 'items-per-page' })
|
||||
itemsPerPage = 20;
|
||||
|
||||
@state()
|
||||
private _redirectData?: RedirectUrl[];
|
||||
|
||||
@state()
|
||||
private _trackerStatus = true;
|
||||
|
||||
@state()
|
||||
private _currentPage = 1;
|
||||
|
||||
@state()
|
||||
private _total?: number;
|
||||
|
||||
@state()
|
||||
private _buttonState: UUIButtonState;
|
||||
|
||||
@state()
|
||||
private _filter?: string;
|
||||
|
||||
@query('#search-input')
|
||||
private _searchField!: HTMLInputElement;
|
||||
|
||||
@query('uui-pagination')
|
||||
private _pagination?: UUIPaginationElement;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (_instance) => {
|
||||
this._modalService = _instance;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._getTrackerStatus();
|
||||
this._getRedirectData();
|
||||
}
|
||||
|
||||
private async _getTrackerStatus() {
|
||||
const { data } = await tryExecuteAndNotify(this, RedirectManagementResource.getRedirectManagementStatus());
|
||||
if (data && data.status) this._trackerStatus = data.status === RedirectStatus.ENABLED ? true : false;
|
||||
}
|
||||
|
||||
private _removeRedirectHandler(data: RedirectUrl) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
headline: 'Delete',
|
||||
content: html`
|
||||
<div style="width:300px">
|
||||
<p>This will remove the redirect</p>
|
||||
Original URL: <strong>${data.originalUrl}</strong><br />
|
||||
Redirected To: <strong>${data.destinationUrl}</strong>
|
||||
<p>Are you sure you want to delete?</p>
|
||||
</div>
|
||||
`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
modalHandler?.onClose().then(({ confirmed }) => {
|
||||
if (confirmed) this._removeRedirect(data);
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeRedirect(r: RedirectUrl) {
|
||||
if (!r.key) return;
|
||||
const res = await tryExecuteAndNotify(
|
||||
this,
|
||||
RedirectManagementResource.deleteRedirectManagementByKey({ key: r.key })
|
||||
);
|
||||
if (!res.error) {
|
||||
// or just run a this._getRedirectData() again?
|
||||
this.shadowRoot?.getElementById(`redirect-key-${r.key}`)?.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private _disableRedirectHandler() {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
headline: 'Disable URL tracker',
|
||||
content: html`Are you sure you want to disable the URL tracker?`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Disable',
|
||||
});
|
||||
modalHandler?.onClose().then(({ confirmed }) => {
|
||||
if (confirmed) this._toggleRedirect();
|
||||
});
|
||||
}
|
||||
|
||||
private async _toggleRedirect() {
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
this,
|
||||
RedirectManagementResource.postRedirectManagementStatus({ status: RedirectStatus.ENABLED })
|
||||
);
|
||||
|
||||
if (!error) {
|
||||
this._trackerStatus = !this._trackerStatus;
|
||||
}
|
||||
}
|
||||
|
||||
private _inputHandler(pressed: KeyboardEvent) {
|
||||
if (pressed.key === 'Enter') this._searchHandler();
|
||||
}
|
||||
|
||||
private async _searchHandler() {
|
||||
this._filter = this._searchField.value;
|
||||
if (this._pagination) this._pagination.current = 1;
|
||||
this._currentPage = 1;
|
||||
if (this._filter.length) {
|
||||
this._buttonState = 'waiting';
|
||||
}
|
||||
this._getRedirectData();
|
||||
}
|
||||
|
||||
private _onPageChange(event: UUIPaginationEvent) {
|
||||
if (this._currentPage === event.target.current) return;
|
||||
this._currentPage = event.target.current;
|
||||
this._getRedirectData();
|
||||
}
|
||||
|
||||
private async _getRedirectData() {
|
||||
const skip = this._currentPage * this.itemsPerPage - this.itemsPerPage;
|
||||
const { data } = await tryExecuteAndNotify(
|
||||
this,
|
||||
RedirectManagementResource.getRedirectManagement({ filter: this._filter, take: this.itemsPerPage, skip })
|
||||
);
|
||||
if (data) {
|
||||
this._total = data?.total;
|
||||
this._redirectData = data?.items;
|
||||
if (this._filter?.length) this._buttonState = 'success';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<h1>Redirect Management</h1>
|
||||
return html`<div class="actions">
|
||||
${this._trackerStatus
|
||||
? html`<div>
|
||||
<uui-input
|
||||
id="search-input"
|
||||
placeholder="Original URL"
|
||||
label="input for search"
|
||||
@keypress="${this._inputHandler}">
|
||||
</uui-input>
|
||||
<uui-button
|
||||
id="search-button"
|
||||
look="primary"
|
||||
color="positive"
|
||||
label="search"
|
||||
.state="${this._buttonState}"
|
||||
@click="${this._searchHandler}">
|
||||
Search<uui-icon name="umb:search"></uui-icon>
|
||||
</uui-button>
|
||||
</div>
|
||||
<uui-button
|
||||
label="Disable URL tracker"
|
||||
look="outline"
|
||||
color="danger"
|
||||
@click="${this._disableRedirectHandler}">
|
||||
Disable URL tracker
|
||||
</uui-button> `
|
||||
: html`<uui-button
|
||||
label="Enable URL tracker"
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click="${this._toggleRedirect}">
|
||||
Enable URL tracker
|
||||
</uui-button>`}
|
||||
</div>
|
||||
|
||||
${this._total && this._total > 0
|
||||
? html`<div class="wrapper ${this._trackerStatus ? 'trackerEnabled' : 'trackerDisabled'}">
|
||||
${this.renderTable()}
|
||||
</div>`
|
||||
: this._filter?.length
|
||||
? this._renderZeroResults()
|
||||
: this.renderNoRedirects()} `;
|
||||
}
|
||||
|
||||
private _renderZeroResults() {
|
||||
return html`<uui-box>
|
||||
<strong>No redirects matching this search criteria</strong>
|
||||
<p>Double check your search for any error or spelling mistakes.</p>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
private renderNoRedirects() {
|
||||
return html`<uui-box>
|
||||
<strong>No redirects have been made</strong>
|
||||
<p>When a published page gets renamed or moved, a redirect will automatically be made to the new page.</p>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
private renderTable() {
|
||||
return html`<uui-box style="--uui-box-default-padding: 0;">
|
||||
<uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell style="width:10%;">Culture</uui-table-head-cell>
|
||||
<uui-table-head-cell>Original URL</uui-table-head-cell>
|
||||
<uui-table-head-cell style="width:10%;"></uui-table-head-cell>
|
||||
<uui-table-head-cell>Redirected To</uui-table-head-cell>
|
||||
<uui-table-head-cell style="width:10%;">Actions</uui-table-head-cell>
|
||||
</uui-table-head>
|
||||
${this._redirectData?.map((data) => {
|
||||
return html` <uui-table-row id="redirect-key-${data.key}">
|
||||
<uui-table-cell> ${data.culture || '*'} </uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<a href="${data.originalUrl || '#'}" target="_blank"> ${data.originalUrl}</a>
|
||||
<uui-icon name="umb:out"></uui-icon>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-icon name="umb:arrow-right"></uui-icon>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<a href="${data.destinationUrl || '#'}" target="_blank"> ${data.destinationUrl}</a>
|
||||
<uui-icon name="umb:out"></uui-icon>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-action-bar style="justify-self: left;">
|
||||
<uui-button
|
||||
label="Delete"
|
||||
look="secondary"
|
||||
.disabled=${!this._trackerStatus}
|
||||
@click="${() => this._removeRedirectHandler(data)}">
|
||||
<uui-icon name="delete"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
})}
|
||||
</uui-table>
|
||||
</uui-box>
|
||||
`;
|
||||
${this._renderPagination()}
|
||||
</uui-scroll-container
|
||||
>`;
|
||||
}
|
||||
|
||||
private _renderPagination() {
|
||||
if (!this._total) return nothing;
|
||||
|
||||
const totalPages = Math.ceil(this._total / this.itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) return nothing;
|
||||
|
||||
return html`<div class="pagination">
|
||||
<uui-pagination .total=${totalPages} @change="${this._onPageChange}"></uui-pagination>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
import { UmbDashboardRedirectManagementElement } from './dashboard-redirect-management.element';
|
||||
import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
|
||||
describe('UmbDashboardRedirectManagement', () => {
|
||||
let element: UmbDashboardRedirectManagementElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<umb-dashboard-redirect-management></umb-dashboard-redirect-management>`);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbDashboardRedirectManagementElement);
|
||||
});
|
||||
|
||||
it('passes the a11y audit', async () => {
|
||||
await expect(element).to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,82 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { IRoute, IRoutingInfo } from 'router-slot';
|
||||
import { UmbDashboardHealthCheckGroupElement } from './views/health-check-group.element';
|
||||
import {
|
||||
UmbHealthCheckDashboardContext,
|
||||
UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN,
|
||||
} from './health-check-dashboard.context';
|
||||
import { UmbHealthCheckContext } from './health-check.context';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { ManifestHealthCheck } from '@umbraco-cms/extensions-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
import { HealthCheckGroup, HealthCheckResource } from '@umbraco-cms/backend-api';
|
||||
|
||||
@customElement('umb-dashboard-health-check')
|
||||
export class UmbDashboardHealthCheckElement extends LitElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
export class UmbDashboardHealthCheckElement extends UmbLitElement {
|
||||
@state()
|
||||
private _routes: IRoute[] = [
|
||||
{
|
||||
path: `/:groupName`,
|
||||
component: () => import('./views/health-check-group.element'),
|
||||
setup: (component: HTMLElement, info: IRoutingInfo) => {
|
||||
const element = component as UmbDashboardHealthCheckGroupElement;
|
||||
element.groupName = decodeURI(info.match.params.groupName);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ``,
|
||||
component: () => import('./views/health-check-overview.element'),
|
||||
},
|
||||
];
|
||||
|
||||
private _healthCheckDashboardContext = new UmbHealthCheckDashboardContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.provideContext(UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN, this._healthCheckDashboardContext);
|
||||
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('healthCheck'), (healthCheckManifests) => {
|
||||
this._healthCheckDashboardContext.manifests = healthCheckManifests;
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this.#registerHealthChecks();
|
||||
}
|
||||
|
||||
#registerHealthChecks = async () => {
|
||||
const { data } = await tryExecuteAndNotify(this, HealthCheckResource.getHealthCheckGroup({ skip: 0, take: 9999 }));
|
||||
if (!data) return;
|
||||
const manifests = this.#createManifests(data.items);
|
||||
this.#register(manifests);
|
||||
};
|
||||
|
||||
#createManifests(groups: HealthCheckGroup[]): Array<ManifestHealthCheck> {
|
||||
return groups.map((group) => {
|
||||
return {
|
||||
type: 'healthCheck',
|
||||
alias: `Umb.HealthCheck.${group.name?.replace(/\s+/g, '') || ''}`,
|
||||
name: `${group.name} Health Check`,
|
||||
weight: 500,
|
||||
meta: {
|
||||
label: group.name || '',
|
||||
api: UmbHealthCheckContext,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#register(manifests: Array<ManifestHealthCheck>) {
|
||||
manifests.forEach((manifest) => {
|
||||
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<h1>Health Check</h1>
|
||||
</uui-box>
|
||||
`;
|
||||
return html` <router-slot .routes=${this._routes}></router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import type { UmbDashboardHealthCheckElement } from './dashboard-health-check.element';
|
||||
import './dashboard-health-check.element';
|
||||
import type { UmbDashboardHealthCheckOverviewElement } from './views/health-check-overview.element';
|
||||
import './views/health-check-overview.element';
|
||||
|
||||
import type { UmbDashboardHealthCheckGroupElement } from './views/health-check-group.element';
|
||||
import './views/health-check-group.element';
|
||||
|
||||
export default {
|
||||
title: 'Dashboards/Health Check',
|
||||
component: 'umb-dashboard-health-check',
|
||||
component: 'umb-dashboard-health-check-overview',
|
||||
id: 'umb-dashboard-health-check',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbDashboardHealthCheckElement> = () =>
|
||||
html` <umb-dashboard-health-check></umb-dashboard-health-check>`;
|
||||
export const AAAOverview: Story<UmbDashboardHealthCheckOverviewElement> = () =>
|
||||
html` <umb-dashboard-health-check-overview></umb-dashboard-health-check-overview>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
|
||||
export const Group: Story<UmbDashboardHealthCheckGroupElement> = () =>
|
||||
html` <umb-dashboard-health-check-group></umb-dashboard-health-check-group>`;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { UmbHealthCheckContext } from './health-check.context';
|
||||
import type { ManifestHealthCheck } from '@umbraco-cms/models';
|
||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||
|
||||
export class UmbHealthCheckDashboardContext {
|
||||
#manifests: ManifestHealthCheck[] = [];
|
||||
set manifests(value: ManifestHealthCheck[]) {
|
||||
this.#manifests = value;
|
||||
this.#registerApis();
|
||||
}
|
||||
get manifests() {
|
||||
return this.#manifests;
|
||||
}
|
||||
|
||||
public apis = new Map<string, UmbHealthCheckContext>();
|
||||
public host: HTMLElement;
|
||||
|
||||
constructor(host: HTMLElement) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
checkAll() {
|
||||
for (const [label, api] of this.apis.entries()) {
|
||||
api?.checkGroup?.(label);
|
||||
}
|
||||
}
|
||||
|
||||
#registerApis() {
|
||||
this.apis.clear();
|
||||
this.#manifests.forEach((manifest) => {
|
||||
// the group name (label) is the unique key for a health check group
|
||||
this.apis.set(manifest.meta.label, new manifest.meta.api(this.host));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN = new UmbContextToken<UmbHealthCheckDashboardContext>(
|
||||
UmbHealthCheckDashboardContext.name
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { HealthCheckResource, HealthCheckWithResult } from '@umbraco-cms/backend-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||
|
||||
export class UmbHealthCheckContext {
|
||||
private _checks: BehaviorSubject<Array<any>> = new BehaviorSubject(<Array<any>>[]);
|
||||
public readonly checks: Observable<Array<any>> = this._checks.asObservable();
|
||||
|
||||
private _results: BehaviorSubject<Array<any>> = new BehaviorSubject(<Array<any>>[]);
|
||||
public readonly results: Observable<Array<any>> = this._results.asObservable();
|
||||
|
||||
public host: UmbControllerHostInterface;
|
||||
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
//TODO: Is this how we want to it?
|
||||
|
||||
async getGroupChecks(name: string) {
|
||||
const { data } = await tryExecuteAndNotify(this.host, HealthCheckResource.getHealthCheckGroupByName({ name }));
|
||||
|
||||
if (data) {
|
||||
data.checks?.forEach((check) => {
|
||||
delete check.results;
|
||||
});
|
||||
this._checks.next(data.checks as HealthCheckWithResult[]);
|
||||
}
|
||||
}
|
||||
|
||||
async checkGroup(name: string) {
|
||||
const { data } = await tryExecuteAndNotify(this.host, HealthCheckResource.getHealthCheckGroupByName({ name }));
|
||||
|
||||
if (data) {
|
||||
const results =
|
||||
data.checks?.map((check) => {
|
||||
return {
|
||||
key: check.key,
|
||||
results: check.results,
|
||||
};
|
||||
}) || [];
|
||||
|
||||
this._results.next(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_HEALTHCHECK_CONTEXT_TOKEN = new UmbContextToken<UmbHealthCheckContext>(UmbHealthCheckContext.name);
|
||||
@@ -0,0 +1,150 @@
|
||||
import { UUIButtonState } from '@umbraco-ui/uui';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
|
||||
import { HealthCheckAction, HealthCheckResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
|
||||
@customElement('umb-dashboard-health-check-action')
|
||||
export class UmbDashboardHealthCheckActionElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
margin: var(--uui-size-space-4) 0;
|
||||
display: block;
|
||||
border-radius: var(--uui-border-radius);
|
||||
background-color: #eee;
|
||||
}
|
||||
form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 20px 25px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action uui-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.action uui-button {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.no-description {
|
||||
color: var(--uui-color-border-emphasis);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.required-value {
|
||||
margin: 0 0 var(--uui-size-space-4);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ reflect: true })
|
||||
action!: HealthCheckAction;
|
||||
|
||||
@state()
|
||||
private _buttonState?: UUIButtonState;
|
||||
|
||||
private async _onActionClick(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
this._buttonState = 'waiting';
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
this,
|
||||
HealthCheckResource.postHealthCheckExecuteAction({ requestBody: this.action })
|
||||
);
|
||||
|
||||
if (error) {
|
||||
this._buttonState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
this._buttonState = 'success';
|
||||
this.dispatchEvent(new CustomEvent('action-executed'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <div class="action">
|
||||
<p>${this.action.description || html`<span class="no-description">This action has no description</span>`}</p>
|
||||
<uui-form>
|
||||
<form @submit=${(e: SubmitEvent) => this._onActionClick(e)}>
|
||||
${this._renderValueRequired()}
|
||||
<uui-button
|
||||
type="submit"
|
||||
look="primary"
|
||||
color="positive"
|
||||
label="${this.action.name || 'Action'}"
|
||||
.state=${this._buttonState}>
|
||||
${this.action.name || 'Action'}
|
||||
</uui-button>
|
||||
</form>
|
||||
</uui-form>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderValueRequired() {
|
||||
if (this.action.valueRequired) {
|
||||
switch (this.action.providedValueValidation) {
|
||||
case 'email':
|
||||
return html` <div class="required-value">
|
||||
<uui-label for="action">Set new value:</uui-label>
|
||||
<uui-input
|
||||
id="action"
|
||||
type="email"
|
||||
@input=${(e: any) => (this.action.providedValue = e.target.value)}
|
||||
placeholder="Value"
|
||||
.value=${this.action.providedValue ?? ''}
|
||||
required></uui-input>
|
||||
</div>`;
|
||||
|
||||
case 'regex':
|
||||
return html`<div class="required-value">
|
||||
<uui-label for="action">Set new value:</uui-label>
|
||||
<uui-input
|
||||
id="action"
|
||||
type="text"
|
||||
pattern="${ifDefined(this.action.providedValueValidationRegex ?? undefined)}"
|
||||
@input=${(e: any) => (this.action.providedValue = e.target.value)}
|
||||
placeholder="Value"
|
||||
.value=${this.action.providedValue ?? ''}
|
||||
required></uui-input>
|
||||
</div>`;
|
||||
|
||||
default:
|
||||
return html`<div class="required-value">
|
||||
<uui-label for="action">Set new value:</uui-label>
|
||||
<uui-input
|
||||
id="action"
|
||||
type="text"
|
||||
@input=${(e: any) => (this.action.providedValue = e.target.value)}
|
||||
placeholder="Value"
|
||||
.value=${this.action.providedValue ?? ''}
|
||||
required></uui-input>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDashboardHealthCheckActionElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dashboard-health-check-action': UmbDashboardHealthCheckActionElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { ensureSlash, path } from 'router-slot';
|
||||
import { UmbHealthCheckContext } from '../health-check.context';
|
||||
import {
|
||||
UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN,
|
||||
UmbHealthCheckDashboardContext,
|
||||
} from '../health-check-dashboard.context';
|
||||
import type { ManifestHealthCheck } from '@umbraco-cms/models';
|
||||
import { StatusResultType } from '@umbraco-cms/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
@customElement('umb-health-check-group-box-overview')
|
||||
export class UmbHealthCheckGroupBoxOverviewElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
.group-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.group-box:hover::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: var(--uui-border-radius);
|
||||
transition: opacity 100ms ease-out 0s;
|
||||
opacity: 0.33;
|
||||
outline-color: var(--uui-color-selected);
|
||||
outline-width: 4px;
|
||||
outline-style: solid;
|
||||
}
|
||||
|
||||
a {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: var(--uui-color-text);
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
uui-icon {
|
||||
padding-right: var(--uui-size-space-2);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: Object })
|
||||
manifest?: ManifestHealthCheck;
|
||||
|
||||
private _healthCheckContext?: UmbHealthCheckDashboardContext;
|
||||
|
||||
private _api?: UmbHealthCheckContext;
|
||||
|
||||
@state()
|
||||
private _tagResults?: any = [];
|
||||
|
||||
@state()
|
||||
private _keyResults?: any = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN, (instance) => {
|
||||
this._healthCheckContext = instance;
|
||||
if (!this._healthCheckContext || !this.manifest?.meta.label) return;
|
||||
this._api = this._healthCheckContext?.apis.get(this.manifest?.meta.label);
|
||||
|
||||
this._api?.results.subscribe((results) => {
|
||||
this._keyResults = results;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<a href="${ensureSlash(path()) + this.manifest?.meta.label}">
|
||||
<uui-box class="group-box"> ${this.manifest?.meta.label} ${this._renderStatus()} </uui-box>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
_renderStatus() {
|
||||
const res: any = [];
|
||||
this._keyResults.forEach((item: any) => {
|
||||
item.results.forEach((result: any) => {
|
||||
res.push(result.resultType);
|
||||
});
|
||||
});
|
||||
this._tagResults = res;
|
||||
return html`<div>${this._renderCheckResults(this.filterResults(this._tagResults))}</div>`;
|
||||
}
|
||||
|
||||
_renderCheckResults(resultObject: any) {
|
||||
return html`${resultObject.success > 0
|
||||
? html`<uui-tag look="secondary" color="positive">
|
||||
<uui-icon name="umb:check"></uui-icon>
|
||||
${resultObject.success}
|
||||
</uui-tag> `
|
||||
: nothing}
|
||||
${resultObject.warning > 0
|
||||
? html`<uui-tag look="secondary" color="warning">
|
||||
<uui-icon name="umb:alert"></uui-icon>
|
||||
${resultObject.warning}
|
||||
</uui-tag>`
|
||||
: nothing}
|
||||
${resultObject.error > 0
|
||||
? html`<uui-tag look="secondary" color="danger">
|
||||
<uui-icon name="umb:wrong"></uui-icon>
|
||||
${resultObject.error}
|
||||
</uui-tag>`
|
||||
: nothing}
|
||||
${resultObject.info > 0
|
||||
? html`<uui-tag look="secondary">
|
||||
<uui-icon name="umb:info"></uui-icon>
|
||||
${resultObject.info}
|
||||
</uui-tag>`
|
||||
: nothing} `;
|
||||
}
|
||||
|
||||
filterResults(results: any): any {
|
||||
const tags = {
|
||||
success: 0,
|
||||
warning: 0,
|
||||
error: 0,
|
||||
info: 0,
|
||||
};
|
||||
|
||||
results.forEach((result: any) => {
|
||||
switch (result) {
|
||||
case StatusResultType.SUCCESS:
|
||||
tags.success += 1;
|
||||
break;
|
||||
case StatusResultType.WARNING:
|
||||
tags.warning += 1;
|
||||
break;
|
||||
case StatusResultType.ERROR:
|
||||
tags.error += 1;
|
||||
break;
|
||||
case StatusResultType.INFO:
|
||||
tags.info += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbHealthCheckGroupBoxOverviewElement;
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-health-check--group-box-overview': UmbHealthCheckGroupBoxOverviewElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { UUIButtonState } from '@umbraco-ui/uui';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
|
||||
import { UmbHealthCheckContext } from '../health-check.context';
|
||||
import {
|
||||
UmbHealthCheckDashboardContext,
|
||||
UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN,
|
||||
} from '../health-check-dashboard.context';
|
||||
import {
|
||||
HealthCheckAction,
|
||||
HealthCheckGroupWithResult,
|
||||
HealthCheckResource,
|
||||
HealthCheckWithResult,
|
||||
StatusResultType,
|
||||
} from '@umbraco-cms/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
import './health-check-action.element';
|
||||
|
||||
@customElement('umb-dashboard-health-check-group')
|
||||
export class UmbDashboardHealthCheckGroupElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-box {
|
||||
margin-bottom: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.check-results-wrapper .check-result {
|
||||
padding-top: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
.check-results-wrapper .check-result:not(:last-child) {
|
||||
border-bottom: 1px solid var(--uui-color-divider-standalone);
|
||||
padding-bottom: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
.check-results-wrapper uui-button {
|
||||
margin-block-start: 1em;
|
||||
}
|
||||
|
||||
.check-result-description {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.check-result-description span {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
uui-icon {
|
||||
vertical-align: sub;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
groupName!: string;
|
||||
|
||||
@state()
|
||||
private _buttonState: UUIButtonState;
|
||||
|
||||
@state()
|
||||
private _group?: HealthCheckGroupWithResult;
|
||||
|
||||
private _healthCheckContext?: UmbHealthCheckDashboardContext;
|
||||
|
||||
@state()
|
||||
private _checks?: HealthCheckWithResult[] | null;
|
||||
|
||||
@state()
|
||||
private _keyResults?: any;
|
||||
|
||||
private _api?: UmbHealthCheckContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN, (instance) => {
|
||||
this._healthCheckContext = instance;
|
||||
|
||||
this._api = this._healthCheckContext?.apis.get(this.groupName);
|
||||
|
||||
this._api?.getGroupChecks(this.groupName);
|
||||
|
||||
this._api?.checks.subscribe((checks) => {
|
||||
this._checks = checks;
|
||||
this._group = { name: this.groupName, checks: this._checks };
|
||||
});
|
||||
|
||||
this._api?.results.subscribe((results) => {
|
||||
this._keyResults = results;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async _buttonHandler() {
|
||||
this._buttonState = 'waiting';
|
||||
this._api?.checkGroup(this.groupName);
|
||||
this._buttonState = 'success';
|
||||
}
|
||||
|
||||
private _onActionClick(action: HealthCheckAction) {
|
||||
return tryExecuteAndNotify(this, HealthCheckResource.postHealthCheckExecuteAction({ requestBody: action }));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <a href="/section/settings/dashboard/health-check"> ← Back to overview </a>
|
||||
${this._group ? this.#renderGroup() : nothing}`;
|
||||
}
|
||||
|
||||
#renderGroup() {
|
||||
return html`
|
||||
<div class="header">
|
||||
<h2>${this._group?.name}</h2>
|
||||
<uui-button
|
||||
label="Perform checks"
|
||||
color="positive"
|
||||
look="primary"
|
||||
.state="${this._buttonState}"
|
||||
@click="${this._buttonHandler}">
|
||||
Perform checks
|
||||
</uui-button>
|
||||
</div>
|
||||
<div class="checks-wrapper">
|
||||
${this._group?.checks?.map((check) => {
|
||||
return html`<uui-box headline="${check.name || '?'}">
|
||||
<p>${check.description}</p>
|
||||
${check.key ? this.renderCheckResults(check.key) : nothing}
|
||||
</uui-box>`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderCheckResults(key: string) {
|
||||
const checkResults = this._keyResults?.find((result: any) => result.key === key);
|
||||
return html`<uui-icon-registry-essential>
|
||||
<div class="check-results-wrapper">
|
||||
${checkResults?.results.map((result: any) => {
|
||||
return html`<div class="check-result">
|
||||
<div class="check-result-description">
|
||||
<span>${this.renderIcon(result.resultType)}</span>
|
||||
<p>${unsafeHTML(result.message)}</p>
|
||||
</div>
|
||||
|
||||
${result.actions ? this.renderActions(result.actions) : nothing}
|
||||
${result.readMoreLink
|
||||
? html`<uui-button
|
||||
label="Read more"
|
||||
color="default"
|
||||
look="primary"
|
||||
target="_blank"
|
||||
href="${result.readMoreLink}">
|
||||
Read more
|
||||
<uui-icon name="umb:out"></uui-icon>
|
||||
</uui-button>`
|
||||
: nothing}
|
||||
</div>`;
|
||||
})}
|
||||
</div>
|
||||
</uui-icon-registry-essential>`;
|
||||
}
|
||||
|
||||
private renderIcon(type?: StatusResultType) {
|
||||
switch (type) {
|
||||
case StatusResultType.SUCCESS:
|
||||
return html`<uui-icon style="color: var(--uui-color-positive);" name="check"></uui-icon>`;
|
||||
case StatusResultType.WARNING:
|
||||
return html`<uui-icon style="color: var(--uui-color-warning);" name="alert"></uui-icon>`;
|
||||
case StatusResultType.ERROR:
|
||||
return html`<uui-icon style="color: var(--uui-color-danger);" name="remove"></uui-icon>`;
|
||||
case StatusResultType.INFO:
|
||||
return html`<uui-icon style="color:black;" name="info"></uui-icon>`;
|
||||
default:
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
private renderActions(actions: HealthCheckAction[]) {
|
||||
if (actions.length)
|
||||
return html` <div class="action-wrapper">
|
||||
${actions.map(
|
||||
(action) =>
|
||||
html`<umb-dashboard-health-check-action
|
||||
.action=${action}
|
||||
@action-executed=${() => this._buttonHandler()}></umb-dashboard-health-check-action>`
|
||||
)}
|
||||
</div>`;
|
||||
else return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDashboardHealthCheckGroupElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dashboard-health-check-group': UmbDashboardHealthCheckGroupElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UUIButtonState } from '@umbraco-ui/uui';
|
||||
|
||||
import {
|
||||
UmbHealthCheckDashboardContext,
|
||||
UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN,
|
||||
} from '../health-check-dashboard.context';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
import { ManifestHealthCheck } from '@umbraco-cms/extensions-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
|
||||
import './health-check-group-box-overview.element';
|
||||
|
||||
@customElement('umb-dashboard-health-check-overview')
|
||||
export class UmbDashboardHealthCheckOverviewElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-box + uui-box {
|
||||
margin-top: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--uui-size-space-4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, auto));
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _buttonState: UUIButtonState;
|
||||
|
||||
private _healthCheckDashboardContext?: UmbHealthCheckDashboardContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_HEALTHCHECK_DASHBOARD_CONTEXT_TOKEN, (instance) => {
|
||||
this._healthCheckDashboardContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
private async _onHealthCheckHandler() {
|
||||
this._healthCheckDashboardContext?.checkAll();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<div slot="headline" class="flex">
|
||||
Health Check
|
||||
<uui-button
|
||||
label="Perform all checks"
|
||||
color="positive"
|
||||
look="primary"
|
||||
.state="${this._buttonState}"
|
||||
@click="${this._onHealthCheckHandler}">
|
||||
Perform all checks
|
||||
</uui-button>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<umb-extension-slot type="healthCheck" default-element="umb-health-check-group-box-overview">
|
||||
</umb-extension-slot>
|
||||
</div>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDashboardHealthCheckOverviewElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dashboard-health-check-overview': UmbDashboardHealthCheckOverviewElement;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,19 @@ const dashboards: Array<ManifestDashboard> = [
|
||||
pathname: 'published-status',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.HealthCheck',
|
||||
name: 'Health Check',
|
||||
elementName: 'umb-dashboard-health-check',
|
||||
loader: () => import('./health-check/dashboard-health-check.element'),
|
||||
weight: 102,
|
||||
meta: {
|
||||
label: 'Health Check',
|
||||
sections: ['Umb.Section.Settings'],
|
||||
pathname: 'health-check',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.Profiling',
|
||||
|
||||
@@ -30,6 +30,12 @@ export class UUICodeBlock extends LitElement {
|
||||
overflow-y: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
pre {
|
||||
max-width: 100%;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { css, nothing } from 'lit';
|
||||
import type { TemplateResult } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { map } from 'rxjs';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { umbExtensionsRegistry , createExtensionElement, isManifestElementableType } from '@umbraco-cms/extensions-api';
|
||||
import { createExtensionElement, isManifestElementableType, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
export type InitializedExtension = { alias: string; weight: number; component: HTMLElement | null };
|
||||
@@ -67,6 +68,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
};
|
||||
this._extensions.push(extensionObject);
|
||||
let component;
|
||||
|
||||
if (isManifestElementableType(extension)) {
|
||||
component = await createExtensionElement(extension);
|
||||
} else {
|
||||
|
||||
@@ -4,12 +4,9 @@ import { customElement, state } from 'lit/decorators.js';
|
||||
import { IRoutingInfo } from 'router-slot';
|
||||
import { first, map } from 'rxjs';
|
||||
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context';
|
||||
import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import type {
|
||||
ManifestDashboard,
|
||||
ManifestDashboardCollection,
|
||||
ManifestWithMeta,
|
||||
} from '@umbraco-cms/models';
|
||||
import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import type { ManifestDashboard, ManifestDashboardCollection, ManifestWithMeta } from '@umbraco-cms/models';
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
@customElement('umb-section-dashboards')
|
||||
@@ -29,7 +26,7 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#scroll-container {
|
||||
flex:1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#router-slot {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { handlers as usersHandlers } from './domains/users.handlers';
|
||||
import { handlers as userGroupsHandlers } from './domains/user-groups.handlers';
|
||||
import { handlers as examineManagementHandlers } from './domains/examine-management.handlers';
|
||||
import { handlers as modelsBuilderHandlers } from './domains/modelsbuilder.handlers';
|
||||
import { handlers as healthCheckHandlers } from './domains/health-check.handlers';
|
||||
import { handlers as profilingHandlers } from './domains/performance-profiling.handlers';
|
||||
import { handlers as documentHandlers } from './domains/document.handlers';
|
||||
import { handlers as mediaHandlers } from './domains/media.handlers';
|
||||
@@ -19,6 +20,7 @@ import { handlers as mediaTypeHandlers } from './domains/media-type.handlers';
|
||||
import { handlers as memberGroupHandlers } from './domains/member-group.handlers';
|
||||
import { handlers as memberTypeHandlers } from './domains/member-type.handlers';
|
||||
import { handlers as templateHandlers } from './domains/template.handlers';
|
||||
import { handlers as redirectManagement } from './domains/redirect-management.handlers';
|
||||
|
||||
const handlers = [
|
||||
serverHandlers.serverVersionHandler,
|
||||
@@ -39,9 +41,11 @@ const handlers = [
|
||||
...memberTypeHandlers,
|
||||
...examineManagementHandlers,
|
||||
...modelsBuilderHandlers,
|
||||
...healthCheckHandlers,
|
||||
...profilingHandlers,
|
||||
...dictionaryHandlers,
|
||||
...templateHandlers,
|
||||
...redirectManagement,
|
||||
];
|
||||
|
||||
switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
import { HealthCheckGroup, HealthCheckGroupWithResult, StatusResultType } from '@umbraco-cms/backend-api';
|
||||
|
||||
export function getGroupByName(name: string) {
|
||||
return healthGroups.find((group) => group.name == name);
|
||||
}
|
||||
|
||||
export const healthGroups: HealthCheckGroupWithResult[] = [
|
||||
{
|
||||
name: 'Configuration',
|
||||
checks: [
|
||||
{
|
||||
key: 'd0f7599e-9b2a-4d9e-9883-81c7edc5616f',
|
||||
name: 'Macro errors',
|
||||
description:
|
||||
'Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.',
|
||||
results: [
|
||||
{
|
||||
message: `MacroErrors are set to 'Throw' which will prevent some or all pages in your site from loading
|
||||
completely if there are any errors in macros. Rectifying this will set the value to 'Inline'. `,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-macro-errors',
|
||||
actions: [
|
||||
{
|
||||
healthCheckKey: 'key123',
|
||||
name: 'Action name',
|
||||
alias: 'Action alias',
|
||||
description: 'Action description',
|
||||
valueRequired: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: '3e2f7b14-4b41-452b-9a30-e67fbc8e1206',
|
||||
name: 'Notification Email Settings',
|
||||
description:
|
||||
"If notifications are used, the 'from' email address should be specified and changed from the default value.",
|
||||
results: [
|
||||
{
|
||||
message: `Notification email is still set to the default value of <strong>your@email.here</strong>.`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-notification-email',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data Integrity',
|
||||
checks: [
|
||||
{
|
||||
key: '73dd0c1c-e0ca-4c31-9564-1dca509788af',
|
||||
name: 'Database data integrity check',
|
||||
description: 'Checks for various data integrity issues in the Umbraco database.',
|
||||
//group: 'Data Integrity',
|
||||
results: [
|
||||
{
|
||||
message: `All document paths are valid`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
{ message: `All media paths are valid`, resultType: StatusResultType.SUCCESS },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Live Environment',
|
||||
checks: [
|
||||
{
|
||||
key: '61214ff3-fc57-4b31-b5cf-1d095c977d6d',
|
||||
name: 'Debug Compilation Mode',
|
||||
description:
|
||||
'Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.',
|
||||
//group: 'Live Environment',
|
||||
results: [
|
||||
{
|
||||
message: `Debug compilation mode is currently enabled. It is recommended to disable this setting before
|
||||
go live.`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-compilation-debug',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Permissions',
|
||||
checks: [
|
||||
{
|
||||
key: '53dba282-4a79-4b67-b958-b29ec40fcc23',
|
||||
name: 'Folder & File Permissions',
|
||||
description: 'Checks that the web server folder and file permissions are set correctly for Umbraco to run.',
|
||||
//group: 'Permissions',
|
||||
results: [
|
||||
{
|
||||
message: `Folder creation`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
{
|
||||
message: `File writing for packages`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
{
|
||||
message: `File writing`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
{
|
||||
message: `Media folder creation`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Security',
|
||||
checks: [
|
||||
{
|
||||
key: '6708ca45-e96e-40b8-a40a-0607c1ca7f28',
|
||||
name: 'Application URL Configuration',
|
||||
description: 'Checks if the Umbraco application URL is configured for your site.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set`,
|
||||
resultType: StatusResultType.WARNING,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-umbraco-application-url',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'ed0d7e40-971e-4be8-ab6d-8cc5d0a6a5b0',
|
||||
name: 'Click-Jacking Protection',
|
||||
description:
|
||||
'Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361 - 'The SSL connection could not be established,
|
||||
see inner exception.'`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-click-jacking',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: '1cf27db3-efc0-41d7-a1bb-ea912064e071',
|
||||
name: 'Content/MIME Sniffing Protection',
|
||||
description: 'Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361 - 'The SSL connection could not be established,
|
||||
see inner exception.'`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-no-sniff',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'e2048c48-21c5-4be1-a80b-8062162df124',
|
||||
name: 'Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))',
|
||||
description:
|
||||
'Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS).',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361 - 'The SSL connection could not be established,
|
||||
see inner exception.'`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-hsts',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'f4d2b02e-28c5-4999-8463-05759fa15c3a',
|
||||
name: 'Cross-site scripting Protection (X-XSS-Protection header)',
|
||||
description:
|
||||
'This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361 - 'The SSL connection could not be established,
|
||||
see inner exception.'`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-xss-protection',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: '92abbaa2-0586-4089-8ae2-9a843439d577',
|
||||
name: 'Excessive Headers',
|
||||
description:
|
||||
'Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361 - 'The SSL connection could not be established,
|
||||
see inner exception.'`,
|
||||
resultType: StatusResultType.WARNING,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-excessive-headers',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'eb66bb3b-1bcd-4314-9531-9da2c1d6d9a7',
|
||||
name: 'HTTPS Configuration',
|
||||
description:
|
||||
'Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.',
|
||||
//group: 'Security',
|
||||
results: [
|
||||
{
|
||||
message: `You are currently viewing the site using HTTPS scheme`,
|
||||
resultType: StatusResultType.SUCCESS,
|
||||
},
|
||||
{
|
||||
message: `The appSetting 'Umbraco:CMS:Global:UseHttps' is set to 'False' in your appSettings.json file,
|
||||
your cookies are not marked as secure.`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-https-config',
|
||||
},
|
||||
{
|
||||
message: `Error pinging the URL https://localhost:44361/ - 'The SSL connection could not be established,
|
||||
see inner exception.'"`,
|
||||
resultType: StatusResultType.ERROR,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-https-request',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
checks: [
|
||||
{
|
||||
key: '1b5d221b-ce99-4193-97cb-5f3261ec73df',
|
||||
name: 'SMTP Settings',
|
||||
description: 'Checks that valid settings for sending emails are in place.',
|
||||
//group: 'Services',
|
||||
results: [
|
||||
{
|
||||
message: `The 'Umbraco:CMS:Global:Smtp' configuration could not be found.`,
|
||||
readMoreLink: 'https://umbra.co/healthchecks-smtp',
|
||||
resultType: StatusResultType.ERROR,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
export const healthGroupsWithoutResult: HealthCheckGroup[] = [
|
||||
{
|
||||
name: 'Configuration',
|
||||
checks: [
|
||||
{
|
||||
key: 'd0f7599e-9b2a-4d9e-9883-81c7edc5616f',
|
||||
name: 'Macro errors',
|
||||
description:
|
||||
'Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.',
|
||||
},
|
||||
{
|
||||
key: '3e2f7b14-4b41-452b-9a30-e67fbc8e1206',
|
||||
name: 'Notification Email Settings',
|
||||
description:
|
||||
"If notifications are used, the 'from' email address should be specified and changed from the default value.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data Integrity',
|
||||
checks: [
|
||||
{
|
||||
key: '73dd0c1c-e0ca-4c31-9564-1dca509788af',
|
||||
name: 'Database data integrity check',
|
||||
description: 'Checks for various data integrity issues in the Umbraco database.',
|
||||
//group: 'Data Integrity',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Live Environment',
|
||||
checks: [
|
||||
{
|
||||
key: '61214ff3-fc57-4b31-b5cf-1d095c977d6d',
|
||||
name: 'Debug Compilation Mode',
|
||||
description:
|
||||
'Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.',
|
||||
//group: 'Live Environment',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Permissions',
|
||||
checks: [
|
||||
{
|
||||
key: '53dba282-4a79-4b67-b958-b29ec40fcc23',
|
||||
name: 'Folder & File Permissions',
|
||||
description: 'Checks that the web server folder and file permissions are set correctly for Umbraco to run.',
|
||||
//group: 'Permissions',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Security',
|
||||
checks: [
|
||||
{
|
||||
key: '6708ca45-e96e-40b8-a40a-0607c1ca7f28',
|
||||
name: 'Application URL Configuration',
|
||||
description: 'Checks if the Umbraco application URL is configured for your site.',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: 'ed0d7e40-971e-4be8-ab6d-8cc5d0a6a5b0',
|
||||
name: 'Click-Jacking Protection',
|
||||
description:
|
||||
'Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: '1cf27db3-efc0-41d7-a1bb-ea912064e071',
|
||||
name: 'Content/MIME Sniffing Protection',
|
||||
description: 'Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: 'e2048c48-21c5-4be1-a80b-8062162df124',
|
||||
name: 'Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))',
|
||||
description:
|
||||
'Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS).',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: 'f4d2b02e-28c5-4999-8463-05759fa15c3a',
|
||||
name: 'Cross-site scripting Protection (X-XSS-Protection header)',
|
||||
description:
|
||||
'This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: '92abbaa2-0586-4089-8ae2-9a843439d577',
|
||||
name: 'Excessive Headers',
|
||||
description:
|
||||
'Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.',
|
||||
//group: 'Security',
|
||||
},
|
||||
{
|
||||
key: 'eb66bb3b-1bcd-4314-9531-9da2c1d6d9a7',
|
||||
name: 'HTTPS Configuration',
|
||||
description:
|
||||
'Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.',
|
||||
//group: 'Security',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
checks: [
|
||||
{
|
||||
key: '1b5d221b-ce99-4193-97cb-5f3261ec73df',
|
||||
name: 'SMTP Settings',
|
||||
description: 'Checks that valid settings for sending emails are in place.',
|
||||
//group: 'Services',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { getGroupByName, healthGroupsWithoutResult } from '../data/health-check.data';
|
||||
|
||||
import { HealthCheckGroup, PagedHealthCheckGroup } from '@umbraco-cms/backend-api';
|
||||
import { umbracoPath } from '@umbraco-cms/utils';
|
||||
|
||||
export const handlers = [
|
||||
rest.get(umbracoPath('/health-check-group'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PagedHealthCheckGroup>({ total: 9999, items: healthGroupsWithoutResult })
|
||||
);
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/health-check-group/:name'), (_req, res, ctx) => {
|
||||
const name = _req.params.name as string;
|
||||
|
||||
if (!name) return;
|
||||
const group = getGroupByName(name);
|
||||
|
||||
if (group) {
|
||||
return res(ctx.status(200), ctx.json<HealthCheckGroup>(group));
|
||||
} else {
|
||||
return res(ctx.status(404));
|
||||
}
|
||||
}),
|
||||
|
||||
rest.post(umbracoPath('/health-check/execute-action'), async (_req, res, ctx) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, (Math.random() + 1) * 1000)); // simulate a delay of 1-2 seconds
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<boolean>(true)
|
||||
);
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,173 @@
|
||||
import { rest } from 'msw';
|
||||
import { umbracoPath } from '@umbraco-cms/utils';
|
||||
import { PagedRedirectUrl, RedirectUrl, RedirectStatus, RedirectUrlStatus } from '@umbraco-cms/backend-api';
|
||||
|
||||
export const handlers = [
|
||||
rest.get(umbracoPath('/redirect-management'), (_req, res, ctx) => {
|
||||
const filter = _req.url.searchParams.get('filter');
|
||||
const skip = parseInt(_req.url.searchParams.get('skip') ?? '0', 10);
|
||||
const take = parseInt(_req.url.searchParams.get('take') ?? '20', 10);
|
||||
|
||||
if (filter) {
|
||||
const filtered: RedirectUrl[] = [];
|
||||
|
||||
PagedRedirectUrlData.items.forEach((item) => {
|
||||
if (item.originalUrl?.includes(filter)) filtered.push(item);
|
||||
});
|
||||
const filteredPagedData: PagedRedirectUrl = {
|
||||
total: filtered.length,
|
||||
items: filtered.slice(skip, skip + take),
|
||||
};
|
||||
return res(ctx.status(200), ctx.json<PagedRedirectUrl>(filteredPagedData));
|
||||
} else {
|
||||
const items = PagedRedirectUrlData.items.slice(skip, skip + take);
|
||||
|
||||
const PagedData: PagedRedirectUrl = {
|
||||
total: PagedRedirectUrlData.total,
|
||||
items,
|
||||
};
|
||||
return res(ctx.status(200), ctx.json<PagedRedirectUrl>(PagedData));
|
||||
}
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/redirect-management/:key'), async (_req, res, ctx) => {
|
||||
const key = _req.params.key as string;
|
||||
if (!key) return res(ctx.status(404));
|
||||
if (key === 'status') return res(ctx.status(200), ctx.json<RedirectUrlStatus>(UrlTracker));
|
||||
|
||||
const PagedRedirectUrlObject = _getRedirectUrlByKey(key);
|
||||
|
||||
return res(ctx.status(200), ctx.json<PagedRedirectUrl>(PagedRedirectUrlObject));
|
||||
}),
|
||||
|
||||
rest.delete(umbracoPath('/redirect-management/:key'), async (_req, res, ctx) => {
|
||||
const key = _req.params.key as string;
|
||||
if (!key) return res(ctx.status(404));
|
||||
|
||||
const PagedRedirectUrlObject = _deleteRedirectUrlByKey(key);
|
||||
|
||||
return res(ctx.status(200), ctx.json<any>(PagedRedirectUrlObject));
|
||||
}),
|
||||
|
||||
/*rest.get(umbracoPath('/redirect-management/status'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<RedirectUrlStatus>(UrlTracker));
|
||||
}),*/
|
||||
|
||||
rest.post(umbracoPath('/redirect-management/status'), async (_req, res, ctx) => {
|
||||
UrlTracker.status = UrlTracker.status === RedirectStatus.ENABLED ? RedirectStatus.DISABLED : RedirectStatus.ENABLED;
|
||||
return res(ctx.status(200), ctx.json<any>(UrlTracker.status));
|
||||
}),
|
||||
];
|
||||
|
||||
// Mock Data
|
||||
|
||||
const UrlTracker: RedirectUrlStatus = { status: RedirectStatus.ENABLED, userIsAdmin: true };
|
||||
|
||||
const _getRedirectUrlByKey = (key: string) => {
|
||||
const PagedResult: PagedRedirectUrl = {
|
||||
total: 0,
|
||||
items: [],
|
||||
};
|
||||
RedirectUrlData.forEach((data) => {
|
||||
if (data.key?.includes(key)) {
|
||||
PagedResult.items.push(data);
|
||||
PagedResult.total++;
|
||||
}
|
||||
});
|
||||
return PagedResult;
|
||||
};
|
||||
|
||||
const _deleteRedirectUrlByKey = (key: string) => {
|
||||
const index = RedirectUrlData.findIndex((data) => data.key === key);
|
||||
if (index > -1) RedirectUrlData.splice(index, 1);
|
||||
const PagedResult: PagedRedirectUrl = {
|
||||
items: RedirectUrlData,
|
||||
total: RedirectUrlData.length,
|
||||
};
|
||||
return PagedResult;
|
||||
};
|
||||
|
||||
const RedirectUrlData: RedirectUrl[] = [
|
||||
{
|
||||
key: '1',
|
||||
created: '2022-12-05T13:59:43.6827244',
|
||||
destinationUrl: 'kitty.com',
|
||||
originalUrl: 'kitty.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9f2',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'umbraco.com',
|
||||
originalUrl: 'umbraco.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9f',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
created: '2022-12-05T13:59:43.6827244',
|
||||
destinationUrl: 'uui.umbraco.com',
|
||||
originalUrl: 'uui.umbraco.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9f23',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'umbracoffee.com',
|
||||
originalUrl: 'umbracoffee.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9fdsaa',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
created: '2022-12-05T13:59:43.6827244',
|
||||
destinationUrl: 'section/settings',
|
||||
originalUrl: 'section/settings/123',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9f2e23',
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'dxp.com',
|
||||
originalUrl: 'dxp.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9fsafsfd',
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
created: '2022-12-05T13:59:43.6827244',
|
||||
destinationUrl: 'google.com',
|
||||
originalUrl: 'google.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9f2cxza',
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'unicorns.com',
|
||||
originalUrl: 'unicorns.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31d9fweds',
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
created: '2022-12-05T13:59:43.6827244',
|
||||
destinationUrl: 'h5yr.com',
|
||||
originalUrl: 'h5yr.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-5208e2b31ddsfsdsfadsfdx9f2',
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'our.umbraco.com',
|
||||
originalUrl: 'our.umbraco.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-52dsacx08e2b31d9dsafdsff',
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
created: '2022-13-05T13:59:43.6827244',
|
||||
destinationUrl: 'your.umbraco.com',
|
||||
originalUrl: 'your.umbraco.dk',
|
||||
contentKey: '7191c911-6747-4824-849e-52dsacx08e2b31d9fsda',
|
||||
},
|
||||
];
|
||||
|
||||
const PagedRedirectUrlData: PagedRedirectUrl = {
|
||||
total: RedirectUrlData.length,
|
||||
items: RedirectUrlData,
|
||||
};
|
||||
@@ -10,6 +10,8 @@ import { handlers as telemetryHandlers } from './domains/telemetry.handlers';
|
||||
import { handlers as examineManagementHandlers } from './domains/examine-management.handlers';
|
||||
import { handlers as modelsBuilderHandlers } from './domains/modelsbuilder.handlers';
|
||||
import { handlers as profileHandlers } from './domains/performance-profiling.handlers';
|
||||
import { handlers as healthCheckHandlers } from './domains/health-check.handlers';
|
||||
import { handlers as redirectManagementHandlers } from './domains/redirect-management.handlers';
|
||||
|
||||
export const handlers = [
|
||||
serverHandlers.serverRunningHandler,
|
||||
@@ -26,4 +28,6 @@ export const handlers = [
|
||||
...examineManagementHandlers,
|
||||
...modelsBuilderHandlers,
|
||||
...profileHandlers,
|
||||
...healthCheckHandlers,
|
||||
...redirectManagementHandlers,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user