fix(typescript): tighten TypeScript null safety and error handling across backend and ops UI
This commit is contained in:
@@ -195,17 +195,18 @@ export class OpsDashboard extends DeesElement {
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
simpleLogin.addEventListener('login', (e: CustomEvent) => {
|
||||
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
||||
simpleLogin.addEventListener('login', (e: Event) => {
|
||||
// Handle logout event
|
||||
this.login(e.detail.data.username, e.detail.data.password);
|
||||
const detail = (e as CustomEvent).detail;
|
||||
this.login(detail.data.username, detail.data.password);
|
||||
});
|
||||
|
||||
// Handle view changes
|
||||
const appDash = this.shadowRoot.querySelector('dees-simple-appdash');
|
||||
const appDash = this.shadowRoot!.querySelector('dees-simple-appdash');
|
||||
if (appDash) {
|
||||
appDash.addEventListener('view-select', (e: CustomEvent) => {
|
||||
const viewName = e.detail.view.name.toLowerCase();
|
||||
appDash.addEventListener('view-select', (e: Event) => {
|
||||
const viewName = (e as CustomEvent).detail.view.name.toLowerCase();
|
||||
// Use router for navigation instead of direct state update
|
||||
appRouter.navigateToView(viewName);
|
||||
});
|
||||
@@ -217,7 +218,7 @@ export class OpsDashboard extends DeesElement {
|
||||
}
|
||||
|
||||
// Handle initial state - check if we have a stored session that's still valid
|
||||
const loginState = appstate.loginStatePart.getState();
|
||||
const loginState = appstate.loginStatePart.getState()!;
|
||||
if (loginState.identity?.jwt) {
|
||||
if (loginState.identity.expiresAt > Date.now()) {
|
||||
// Client-side expiry looks valid — verify with server (keypair may have changed)
|
||||
@@ -229,7 +230,7 @@ export class OpsDashboard extends DeesElement {
|
||||
if (response.valid) {
|
||||
// JWT confirmed valid by server
|
||||
this.loginState = loginState;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await (simpleLogin as any).switchToSlottedContent();
|
||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
|
||||
} else {
|
||||
@@ -250,8 +251,8 @@ export class OpsDashboard extends DeesElement {
|
||||
private async login(username: string, password: string) {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
console.log(`Attempting to login...`);
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
const form = simpleLogin.shadowRoot.querySelector('dees-form');
|
||||
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
||||
const form = simpleLogin.shadowRoot!.querySelector('dees-form') as any;
|
||||
form.setStatus('pending', 'Logging in...');
|
||||
|
||||
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
||||
@@ -262,14 +263,14 @@ export class OpsDashboard extends DeesElement {
|
||||
if (state.identity) {
|
||||
console.log('Login successful');
|
||||
this.loginState = state;
|
||||
form.setStatus('success', 'Logged in!');
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
form!.setStatus('success', 'Logged in!');
|
||||
await simpleLogin!.switchToSlottedContent();
|
||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
|
||||
} else {
|
||||
form.setStatus('error', 'Login failed!');
|
||||
form!.setStatus('error', 'Login failed!');
|
||||
await domtools.convenience.smartdelay.delayFor(2000);
|
||||
form.reset();
|
||||
form!.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ declare global {
|
||||
@customElement('ops-view-certificates')
|
||||
export class OpsViewCertificates extends DeesElement {
|
||||
@state()
|
||||
accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState();
|
||||
accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState()!;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -264,10 +264,10 @@ export class OpsViewCertificates extends DeesElement {
|
||||
{
|
||||
name: 'Import',
|
||||
iconName: 'lucide:upload',
|
||||
action: async (modal) => {
|
||||
action: async (modal: any) => {
|
||||
const { DeesToast } = await import('@design.estate/dees-catalog');
|
||||
try {
|
||||
const form = modal.shadowRoot.querySelector('dees-form') as any;
|
||||
const form = modal.shadowRoot!.querySelector('dees-form') as any;
|
||||
const formData = await form.collectFormData();
|
||||
const files = formData.certJsonFile;
|
||||
if (!files || files.length === 0) {
|
||||
@@ -287,8 +287,8 @@ export class OpsViewCertificates extends DeesElement {
|
||||
);
|
||||
DeesToast.show({ message: `Certificate imported for ${cert.domainName}`, type: 'success', duration: 3000 });
|
||||
modal.destroy();
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Import failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
} catch (err: unknown) {
|
||||
DeesToast.show({ message: `Import failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -339,8 +339,8 @@ export class OpsViewCertificates extends DeesElement {
|
||||
} else {
|
||||
DeesToast.show({ message: response.message || 'Export failed', type: 'error', duration: 4000 });
|
||||
}
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Export failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
} catch (err: unknown) {
|
||||
DeesToast.show({ message: `Export failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -363,7 +363,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
{
|
||||
name: 'Delete',
|
||||
iconName: 'lucide:trash-2',
|
||||
action: async (modal) => {
|
||||
action: async (modal: any) => {
|
||||
try {
|
||||
await appstate.certificateStatePart.dispatchAction(
|
||||
appstate.deleteCertificateAction,
|
||||
@@ -371,8 +371,8 @@ export class OpsViewCertificates extends DeesElement {
|
||||
);
|
||||
DeesToast.show({ message: `Certificate deleted for ${cert.domain}`, type: 'success', duration: 3000 });
|
||||
modal.destroy();
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Delete failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
} catch (err: unknown) {
|
||||
DeesToast.show({ message: `Delete failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -102,7 +102,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult {
|
||||
private renderSystemSection(sys: NonNullable<appstate.IConfigState['config']>['system']): TemplateResult {
|
||||
// Annotate proxy IPs with source hint when Remote Ingress is active
|
||||
const ri = this.configState.config?.remoteIngress;
|
||||
let proxyIpValues: string[] | null = sys.proxyIps.length > 0 ? [...sys.proxyIps] : null;
|
||||
@@ -133,7 +133,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderSmartProxySection(proxy: appstate.IConfigState['config']['smartProxy']): TemplateResult {
|
||||
private renderSmartProxySection(proxy: NonNullable<appstate.IConfigState['config']>['smartProxy']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Route Count', value: proxy.routeCount },
|
||||
];
|
||||
@@ -164,7 +164,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderEmailSection(email: appstate.IConfigState['config']['email']): TemplateResult {
|
||||
private renderEmailSection(email: NonNullable<appstate.IConfigState['config']>['email']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Ports', value: email.ports.length > 0 ? email.ports.map(String) : null, type: 'pills' },
|
||||
{ key: 'Hostname', value: email.hostname },
|
||||
@@ -196,7 +196,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDnsSection(dns: appstate.IConfigState['config']['dns']): TemplateResult {
|
||||
private renderDnsSection(dns: NonNullable<appstate.IConfigState['config']>['dns']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Port', value: dns.port },
|
||||
{ key: 'NS Domains', value: dns.nsDomains.length > 0 ? dns.nsDomains : null, type: 'pills' },
|
||||
@@ -216,7 +216,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderTlsSection(tls: appstate.IConfigState['config']['tls']): TemplateResult {
|
||||
private renderTlsSection(tls: NonNullable<appstate.IConfigState['config']>['tls']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Contact Email', value: tls.contactEmail },
|
||||
{ key: 'Domain', value: tls.domain },
|
||||
@@ -242,7 +242,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderCacheSection(cache: appstate.IConfigState['config']['cache']): TemplateResult {
|
||||
private renderCacheSection(cache: NonNullable<appstate.IConfigState['config']>['cache']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Storage Path', value: cache.storagePath },
|
||||
{ key: 'DB Name', value: cache.dbName },
|
||||
@@ -267,7 +267,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderRadiusSection(radius: appstate.IConfigState['config']['radius']): TemplateResult {
|
||||
private renderRadiusSection(radius: NonNullable<appstate.IConfigState['config']>['radius']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Auth Port', value: radius.authPort },
|
||||
{ key: 'Accounting Port', value: radius.acctPort },
|
||||
@@ -296,7 +296,7 @@ export class OpsViewConfig extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderRemoteIngressSection(ri: appstate.IConfigState['config']['remoteIngress']): TemplateResult {
|
||||
private renderRemoteIngressSection(ri: NonNullable<appstate.IConfigState['config']>['remoteIngress']): TemplateResult {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
||||
{ key: 'Hub Domain', value: ri.hubDomain },
|
||||
|
||||
@@ -83,13 +83,13 @@ export class OpsViewEmails extends DeesElement {
|
||||
private async handleEmailClick(e: CustomEvent<interfaces.requests.IEmail>) {
|
||||
const emailSummary = e.detail;
|
||||
try {
|
||||
const context = appstate.loginStatePart.getState();
|
||||
const context = appstate.loginStatePart.getState()!;
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_GetEmailDetail
|
||||
>('/typedrequest', 'getEmailDetail');
|
||||
|
||||
const response = await request.fire({
|
||||
identity: context.identity,
|
||||
identity: context.identity!,
|
||||
emailId: emailSummary.id,
|
||||
});
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ interface INetworkRequest {
|
||||
@customElement('ops-view-network')
|
||||
export class OpsViewNetwork extends DeesElement {
|
||||
@state()
|
||||
accessor statsState = appstate.statsStatePart.getState();
|
||||
accessor statsState = appstate.statsStatePart.getState()!;
|
||||
|
||||
@state()
|
||||
accessor networkState = appstate.networkStatePart.getState();
|
||||
accessor networkState = appstate.networkStatePart.getState()!;
|
||||
|
||||
|
||||
@state()
|
||||
|
||||
@@ -21,7 +21,7 @@ declare global {
|
||||
@customElement('ops-view-remoteingress')
|
||||
export class OpsViewRemoteIngress extends DeesElement {
|
||||
@state()
|
||||
accessor riState: appstate.IRemoteIngressState = appstate.remoteIngressStatePart.getState();
|
||||
accessor riState: appstate.IRemoteIngressState = appstate.remoteIngressStatePart.getState()!;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -184,7 +184,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
@click=${async () => {
|
||||
const { DeesToast } = await import('@design.estate/dees-catalog');
|
||||
try {
|
||||
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId);
|
||||
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId!);
|
||||
if (response.success && response.token) {
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||
await navigator.clipboard.writeText(response.token);
|
||||
@@ -202,8 +202,8 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
} else {
|
||||
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
|
||||
}
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
} catch (err: unknown) {
|
||||
DeesToast.show({ message: `Failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
}}
|
||||
>Copy Connection Token</dees-button>
|
||||
@@ -399,8 +399,8 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
} else {
|
||||
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
|
||||
}
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
} catch (err: unknown) {
|
||||
DeesToast.show({ message: `Failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user