solved problem on container import
This commit is contained in:
parent
f57f5be073
commit
5f405b69a0
|
|
@ -12,14 +12,7 @@ import { AdminCreateUserRequest, UserRole } from '@models/user';
|
|||
import { Router } from '@angular/router';
|
||||
import { Authorization } from '@services/authorization/authorization';
|
||||
import { FormUtility } from '@utilities/form-utility';
|
||||
|
||||
function hasValidPasswordComplexity(value: string): boolean {
|
||||
return /^\S+$/.test(value)
|
||||
&& /[a-z]/.test(value)
|
||||
&& /[A-Z]/.test(value)
|
||||
&& /\d/.test(value)
|
||||
&& /[^A-Za-z0-9]/.test(value);
|
||||
}
|
||||
import { hasValidPasswordComplexity, evaluatePasswordChecks, initialPasswordChecks } from '@utilities/password-validation';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-create-user-page',
|
||||
|
|
@ -76,28 +69,14 @@ export class AdminCreateUserPage extends FormUtility {
|
|||
});
|
||||
});
|
||||
|
||||
readonly createPasswordChecks = signal([
|
||||
{ label: 'At least 8 characters', satisfied: false },
|
||||
{ label: 'At least one lowercase letter', satisfied: false },
|
||||
{ label: 'At least one uppercase letter', satisfied: false },
|
||||
{ label: 'At least one number', satisfied: false },
|
||||
{ label: 'At least one special character', satisfied: false },
|
||||
{ label: 'No spaces', satisfied: true }
|
||||
]);
|
||||
readonly createPasswordChecks = signal(initialPasswordChecks());
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
effect(() => {
|
||||
const password = this.createModel().password;
|
||||
this.createPasswordChecks.set([
|
||||
{ label: 'At least 8 characters', satisfied: password.length >= 8 },
|
||||
{ label: 'At least one lowercase letter', satisfied: /[a-z]/.test(password) },
|
||||
{ label: 'At least one uppercase letter', satisfied: /[A-Z]/.test(password) },
|
||||
{ label: 'At least one number', satisfied: /\d/.test(password) },
|
||||
{ label: 'At least one special character', satisfied: /[^A-Za-z0-9]/.test(password) },
|
||||
{ label: 'No spaces', satisfied: /^\S*$/.test(password) }
|
||||
]);
|
||||
this.createPasswordChecks.set(evaluatePasswordChecks(password));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,7 @@ import { Authorization } from '@services/authorization/authorization';
|
|||
import { ConfirmDialogService } from '@services/dialogs/confirm-dialog';
|
||||
import { AdminResetPasswordDialogComponent } from '@shared/admin-reset-password-dialog/admin-reset-password-dialog';
|
||||
import { FormUtility } from '@utilities/form-utility';
|
||||
|
||||
function hasValidPasswordComplexity(value: string): boolean {
|
||||
return /^\S+$/.test(value)
|
||||
&& /[a-z]/.test(value)
|
||||
&& /[A-Z]/.test(value)
|
||||
&& /\d/.test(value)
|
||||
&& /[^A-Za-z0-9]/.test(value);
|
||||
}
|
||||
import { hasValidPasswordComplexity, evaluatePasswordChecks, initialPasswordChecks } from '@utilities/password-validation';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-users',
|
||||
|
|
@ -95,28 +88,14 @@ export class AdminUsersPage extends FormUtility {
|
|||
});
|
||||
});
|
||||
|
||||
readonly createPasswordChecks = signal([
|
||||
{ label: 'At least 8 characters', satisfied: false },
|
||||
{ label: 'At least one lowercase letter', satisfied: false },
|
||||
{ label: 'At least one uppercase letter', satisfied: false },
|
||||
{ label: 'At least one number', satisfied: false },
|
||||
{ label: 'At least one special character', satisfied: false },
|
||||
{ label: 'No spaces', satisfied: true }
|
||||
]);
|
||||
readonly createPasswordChecks = signal(initialPasswordChecks());
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
effect(() => {
|
||||
const password = this.createModel().password;
|
||||
this.createPasswordChecks.set([
|
||||
{ label: 'At least 8 characters', satisfied: password.length >= 8 },
|
||||
{ label: 'At least one lowercase letter', satisfied: /[a-z]/.test(password) },
|
||||
{ label: 'At least one uppercase letter', satisfied: /[A-Z]/.test(password) },
|
||||
{ label: 'At least one number', satisfied: /\d/.test(password) },
|
||||
{ label: 'At least one special character', satisfied: /[^A-Za-z0-9]/.test(password) },
|
||||
{ label: 'No spaces', satisfied: /^\S*$/.test(password) }
|
||||
]);
|
||||
this.createPasswordChecks.set(evaluatePasswordChecks(password));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,7 @@ import { environment } from '@environment';
|
|||
import { UserRegistration } from '@models/user';
|
||||
import { Authorization } from '@services/authorization/authorization';
|
||||
import { FormUtility } from '@utilities/form-utility';
|
||||
|
||||
function hasValidPasswordComplexity(value: string): boolean {
|
||||
return /^\S+$/.test(value)
|
||||
&& /[a-z]/.test(value)
|
||||
&& /[A-Z]/.test(value)
|
||||
&& /\d/.test(value)
|
||||
&& /[^A-Za-z0-9]/.test(value);
|
||||
}
|
||||
import { hasValidPasswordComplexity } from '@utilities/password-validation';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from '@models/user';
|
||||
import { AuthorizationCallServiceBase } from './authorization-call.base';
|
||||
import { environment } from '@environment';
|
||||
import { Observable, take, tap } from 'rxjs';
|
||||
import { Observable, catchError, take, tap, throwError } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -31,9 +31,17 @@ export class Authorization {
|
|||
tap(res => {
|
||||
const normalizedUser = this.normalizeUser(res);
|
||||
this.user.set(normalizedUser);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(Authorization.USER_STORAGE_KEY, JSON.stringify(normalizedUser));
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(Authorization.USER_STORAGE_KEY, JSON.stringify(normalizedUser));
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage errors (e.g. quota exceeded, private browsing).
|
||||
}
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error('Login failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -102,8 +110,12 @@ export class Authorization {
|
|||
return this.authCall.logout().pipe(
|
||||
take(1),
|
||||
tap(() => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem(Authorization.USER_STORAGE_KEY);
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem(Authorization.USER_STORAGE_KEY);
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage errors.
|
||||
}
|
||||
this.user.set(null);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -74,8 +74,10 @@ export class ContainersCallServiceFake extends ContainersCallServiceBase {
|
|||
|
||||
override createContainer(containerId: string, configuration: any): Observable<FlowContainer> {
|
||||
const typeName = String(configuration?.typeName ?? configuration?.type ?? 'GenericContainer');
|
||||
const configurationType = this.resolveConfigurationType(typeName, configuration);
|
||||
const specificConfiguration = {
|
||||
...configuration,
|
||||
type: configurationType,
|
||||
name: typeof configuration?.name === 'string' && configuration.name.length > 0
|
||||
? configuration.name
|
||||
: 'Container'
|
||||
|
|
@ -128,4 +130,39 @@ export class ContainersCallServiceFake extends ContainersCallServiceBase {
|
|||
openOutputs: []
|
||||
});
|
||||
}
|
||||
|
||||
private resolveConfigurationType(containerType: string, configuration: Record<string, unknown>) {
|
||||
const explicitType = configuration['type'];
|
||||
if (typeof explicitType === 'string' && explicitType.length > 0) {
|
||||
return explicitType;
|
||||
}
|
||||
|
||||
const descriptor = this.containerTypes.find((candidate) => candidate.type === containerType);
|
||||
const schemaType = this.resolveConfigurationTypeFromSchema(descriptor?.schema);
|
||||
if (schemaType) return schemaType;
|
||||
|
||||
return descriptor?.configurationType ?? 'GenericContainerConfiguration';
|
||||
}
|
||||
|
||||
private resolveConfigurationTypeFromSchema(schema: Record<string, unknown> | null | undefined) {
|
||||
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return null;
|
||||
|
||||
const properties = schema['properties'];
|
||||
if (!properties || typeof properties !== 'object' || Array.isArray(properties)) return null;
|
||||
|
||||
const typeProperty = (properties as Record<string, unknown>)['type'];
|
||||
if (!typeProperty || typeof typeProperty !== 'object' || Array.isArray(typeProperty)) return null;
|
||||
|
||||
const typeSchema = typeProperty as Record<string, unknown>;
|
||||
if (typeof typeSchema['default'] === 'string' && typeSchema['default'].length > 0) {
|
||||
return typeSchema['default'];
|
||||
}
|
||||
|
||||
const enumValues = Array.isArray(typeSchema['enum'])
|
||||
? typeSchema['enum'].filter((value): value is string => typeof value === 'string' && value.length > 0)
|
||||
: [];
|
||||
if (enumValues.length === 1) return enumValues[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,11 +225,46 @@ export class ContainersCallService extends ContainersCallServiceBase {
|
|||
|
||||
private buildContainerConfigurationPayload(containerType: string, configuration: Record<string, unknown>) {
|
||||
const { typeName: _ignoreTypeName, ...sanitized } = configuration;
|
||||
const configurationType = this.resolveConfigurationType(containerType, sanitized);
|
||||
return {
|
||||
...sanitized,
|
||||
type: configurationType,
|
||||
name: typeof sanitized["name"] === "string" && sanitized["name"].length > 0
|
||||
? sanitized["name"]
|
||||
: containerType
|
||||
};
|
||||
}
|
||||
|
||||
private resolveConfigurationType(containerType: string, configuration: Record<string, unknown>) {
|
||||
const explicitType = this.toNullableString(configuration["type"]);
|
||||
if (explicitType) return explicitType;
|
||||
|
||||
const descriptor = this.containerTypesCache
|
||||
?.find((candidate) => candidate.type === containerType);
|
||||
const schemaType = this.resolveConfigurationTypeFromSchema(descriptor?.schema);
|
||||
if (schemaType) return schemaType;
|
||||
|
||||
return descriptor?.configurationType ?? "GenericContainerConfiguration";
|
||||
}
|
||||
|
||||
private resolveConfigurationTypeFromSchema(schema: Record<string, unknown> | null | undefined) {
|
||||
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return null;
|
||||
|
||||
const properties = schema["properties"];
|
||||
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return null;
|
||||
|
||||
const typeProperty = (properties as Record<string, unknown>)["type"];
|
||||
if (!typeProperty || typeof typeProperty !== "object" || Array.isArray(typeProperty)) return null;
|
||||
|
||||
const typeSchema = typeProperty as Record<string, unknown>;
|
||||
const defaultValue = this.toNullableString(typeSchema["default"]);
|
||||
if (defaultValue) return defaultValue;
|
||||
|
||||
const enumValues = Array.isArray(typeSchema["enum"])
|
||||
? typeSchema["enum"].filter((value): value is string => typeof value === "string" && value.length > 0)
|
||||
: [];
|
||||
if (enumValues.length === 1) return enumValues[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { DestroyRef, inject, Injectable, signal } from '@angular/core';
|
|||
import { environment } from '@environment';
|
||||
import { LLMDescriptor } from '@models/flow';
|
||||
import { ExecutionEventLogEntry, getExecutionStatusGroup, TaskExecution } from '@models/task-execution';
|
||||
import { catchError, finalize, tap, throwError } from 'rxjs';
|
||||
import { catchError, finalize, Observable, tap, throwError } from 'rxjs';
|
||||
import { TaskExecutionsCallServiceBase } from './task-executions-call.base';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -43,11 +43,10 @@ export class TaskExecutionsService {
|
|||
}
|
||||
|
||||
retrieveExecutionEvents(executionId: string) {
|
||||
return this.taskExecutionsCallService.retrieveExecutionEvents(executionId).pipe(
|
||||
catchError((err) => {
|
||||
console.error('Retrieve execution events failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.retrieveExecutionEvents(executionId),
|
||||
'Retrieve execution events failed',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -64,22 +63,16 @@ export class TaskExecutionsService {
|
|||
}
|
||||
|
||||
deleteExecution(executionId: string) {
|
||||
return this.taskExecutionsCallService.deleteTaskExecution(executionId).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Delete execution failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.deleteTaskExecution(executionId),
|
||||
'Delete execution failed'
|
||||
);
|
||||
}
|
||||
|
||||
startExecution(executionId: string) {
|
||||
return this.taskExecutionsCallService.startTaskExecution(executionId).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Start execution failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.startTaskExecution(executionId),
|
||||
'Start execution failed'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -92,22 +85,16 @@ export class TaskExecutionsService {
|
|||
return throwError(() => new Error('A simulator descriptor is required to start simulation.'));
|
||||
}
|
||||
|
||||
return this.taskExecutionsCallService.simulateTaskExecution(executionId, simulator).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Simulate execution failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.simulateTaskExecution(executionId, simulator),
|
||||
'Simulate execution failed'
|
||||
);
|
||||
}
|
||||
|
||||
cancelExecution(executionId: string) {
|
||||
return this.taskExecutionsCallService.cancelTaskExecution(executionId).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Cancel execution failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.cancelTaskExecution(executionId),
|
||||
'Cancel execution failed'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -118,92 +105,65 @@ export class TaskExecutionsService {
|
|||
return throwError(() => new Error('Resume is only supported for suspended executions.'));
|
||||
}
|
||||
|
||||
return this.taskExecutionsCallService.resumeTaskExecution(executionId).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Resume execution failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.resumeTaskExecution(executionId),
|
||||
'Resume execution failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareStringInput(executionId: string, nodeId: string, inputName: string, value: string) {
|
||||
return this.taskExecutionsCallService.prepareStringInput(executionId, nodeId, inputName, value).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare string input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareStringInput(executionId, nodeId, inputName, value),
|
||||
'Prepare string input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareStringArrayInput(executionId: string, nodeId: string, inputName: string, values: string[]) {
|
||||
return this.taskExecutionsCallService.prepareStringArrayInput(executionId, nodeId, inputName, values).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare string array input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareStringArrayInput(executionId, nodeId, inputName, values),
|
||||
'Prepare string array input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareFileInput(executionId: string, nodeId: string, inputName: string, file: File) {
|
||||
return this.taskExecutionsCallService.prepareFileInput(executionId, nodeId, inputName, file).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare file input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareFileInput(executionId, nodeId, inputName, file),
|
||||
'Prepare file input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareFileArrayInput(executionId: string, nodeId: string, inputName: string, files: File[]) {
|
||||
return this.taskExecutionsCallService.prepareFileArrayInput(executionId, nodeId, inputName, files).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare file array input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareFileArrayInput(executionId, nodeId, inputName, files),
|
||||
'Prepare file array input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareGlobalStringInput(executionId: string, inputName: string, value: string) {
|
||||
return this.taskExecutionsCallService.prepareGlobalStringInput(executionId, inputName, value).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare global string input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareGlobalStringInput(executionId, inputName, value),
|
||||
'Prepare global string input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareGlobalStringArrayInput(executionId: string, inputName: string, values: string[]) {
|
||||
return this.taskExecutionsCallService.prepareGlobalStringArrayInput(executionId, inputName, values).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare global string array input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareGlobalStringArrayInput(executionId, inputName, values),
|
||||
'Prepare global string array input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareGlobalFileInput(executionId: string, inputName: string, file: File) {
|
||||
return this.taskExecutionsCallService.prepareGlobalFileInput(executionId, inputName, file).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare global file input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareGlobalFileInput(executionId, inputName, file),
|
||||
'Prepare global file input failed'
|
||||
);
|
||||
}
|
||||
|
||||
prepareGlobalFileArrayInput(executionId: string, inputName: string, files: File[]) {
|
||||
return this.taskExecutionsCallService.prepareGlobalFileArrayInput(executionId, inputName, files).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Prepare global file array input failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.prepareGlobalFileArrayInput(executionId, inputName, files),
|
||||
'Prepare global file array input failed'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -213,22 +173,16 @@ export class TaskExecutionsService {
|
|||
return throwError(() => new Error('Manual interaction is disabled for simulated executions.'));
|
||||
}
|
||||
|
||||
return this.taskExecutionsCallService.submitInteractionText(executionId, nodeId, fieldName, value).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Submit interaction text failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.submitInteractionText(executionId, nodeId, fieldName, value),
|
||||
'Submit interaction text failed'
|
||||
);
|
||||
}
|
||||
|
||||
provideAuthorization(executionId: string, key: string, value: string) {
|
||||
return this.taskExecutionsCallService.provideAuthorization(executionId, key, value).pipe(
|
||||
tap(() => this.refresh()),
|
||||
catchError((err) => {
|
||||
console.error('Provide authorization failed', err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
return this.withRefreshAndErrorHandling(
|
||||
this.taskExecutionsCallService.provideAuthorization(executionId, key, value),
|
||||
'Provide authorization failed'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -257,4 +211,16 @@ export class TaskExecutionsService {
|
|||
clearInterval(this.pollTimer);
|
||||
this.pollTimer = null;
|
||||
}
|
||||
|
||||
private withRefreshAndErrorHandling<T>(source: Observable<T>, errorMessage: string, refresh = true): Observable<T> {
|
||||
const piped = refresh
|
||||
? source.pipe(tap(() => this.refresh()))
|
||||
: source;
|
||||
return piped.pipe(
|
||||
catchError((err) => {
|
||||
console.error(errorMessage, err);
|
||||
return throwError(() => err);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { Flow } from '@models/flow';
|
|||
import { AssistantService } from '@services/assistant/assistant';
|
||||
import { Authorization } from '@services/authorization/authorization';
|
||||
import { EditorStateHolder } from '@stores/flow-editor';
|
||||
import { finalize, take } from 'rxjs';
|
||||
import { finalize, interval, Subscription, switchMap, take } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-flow-assistant',
|
||||
|
|
@ -31,7 +31,7 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
private readonly assistant = inject(AssistantService);
|
||||
private readonly editorState = inject(EditorStateHolder);
|
||||
private readonly authorization = inject(Authorization);
|
||||
private pollTick: ReturnType<typeof setInterval> | null = null;
|
||||
private pollSubscription: Subscription | null = null;
|
||||
|
||||
readonly assistantConfig = signal<AssistantConfig | null>(null);
|
||||
readonly sessionState = signal<AssistantSessionState | null>(null);
|
||||
|
|
@ -264,24 +264,22 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
|
||||
private startPolling(callId: string, sessionId: string) {
|
||||
this.stopPolling();
|
||||
this.pollTick = setInterval(() => {
|
||||
this.assistant.getCall(callId).pipe(
|
||||
take(1)
|
||||
).subscribe({
|
||||
next: (callState) => {
|
||||
this.currentCall.set(callState);
|
||||
if (callState.status === 'COMPLETED' || callState.status === 'FAILED') {
|
||||
this.stopPolling();
|
||||
void this.reloadSession(sessionId, callState.status === 'FAILED' ? callState.errorMessage : undefined);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Assistant call polling failed', err);
|
||||
this.pollSubscription = interval(500).pipe(
|
||||
switchMap(() => this.assistant.getCall(callId))
|
||||
).subscribe({
|
||||
next: (callState) => {
|
||||
this.currentCall.set(callState);
|
||||
if (callState.status === 'COMPLETED' || callState.status === 'FAILED') {
|
||||
this.stopPolling();
|
||||
this.pushLocalAssistantMessage('Polling the assistant call failed.');
|
||||
void this.reloadSession(sessionId, callState.status === 'FAILED' ? callState.errorMessage : undefined);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Assistant call polling failed', err);
|
||||
this.stopPolling();
|
||||
this.pushLocalAssistantMessage('Polling the assistant call failed.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async reloadSession(sessionId: string, failureMessage?: string) {
|
||||
|
|
@ -378,10 +376,8 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private stopPolling() {
|
||||
if (this.pollTick) {
|
||||
clearInterval(this.pollTick);
|
||||
this.pollTick = null;
|
||||
}
|
||||
this.pollSubscription?.unsubscribe();
|
||||
this.pollSubscription = null;
|
||||
}
|
||||
|
||||
private phaseText(phase: AssistantCallPhase): string {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ChangeDetectionStrategy, Component, effect, Input, input, model, output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, effect, input, model, output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
|
@ -19,7 +19,7 @@ export type OrderField = { field: string; label?: string };
|
|||
})
|
||||
export class Ordering {
|
||||
|
||||
@Input({required : true}) orderView!: OrderViewState;
|
||||
readonly orderView = input.required<OrderViewState>();
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
|
|
@ -34,9 +34,10 @@ export class Ordering {
|
|||
orderBy = model<string | null>(null);
|
||||
|
||||
ngOnInit() {
|
||||
if (this.orderView.orderBy) {
|
||||
this.orderBy.set(this.orderView.orderBy);
|
||||
this.orderDir.set(this.orderView.orderDir);
|
||||
const view = this.orderView();
|
||||
if (view.orderBy) {
|
||||
this.orderBy.set(view.orderBy);
|
||||
this.orderDir.set(view.orderDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ChangeDetectionStrategy, Component, effect, ElementRef, HostListener, Injector, input, OnChanges, OnDestroy, output, signal, SimpleChanges, untracked, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, effect, ElementRef, HostListener, Injector, input, OnChanges, OnDestroy, output, signal, SimpleChanges, untracked, viewChild } from '@angular/core';
|
||||
import { BlockType, FlowData, FlowNode } from '@models/flow';
|
||||
import { Drag } from 'rete-area-plugin';
|
||||
import { BlocksService } from '@services/blocks/blocks';
|
||||
|
|
@ -40,8 +40,8 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
@ViewChild("editor") container!: ElementRef;
|
||||
@ViewChild("shell") shell!: ElementRef<HTMLElement>;
|
||||
readonly container = viewChild.required<ElementRef>('editor');
|
||||
readonly shell = viewChild.required<ElementRef<HTMLElement>>('shell');
|
||||
|
||||
private rete?: ReteEditorInstance;
|
||||
private viewReady = false;
|
||||
|
|
@ -158,7 +158,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
this.selectionPointerId = event.pointerId;
|
||||
this.selectionStart = { x: event.clientX, y: event.clientY };
|
||||
this.selectionBox.set({ left: 0, top: 0, width: 0, height: 0 });
|
||||
this.shell.nativeElement.setPointerCapture(event.pointerId);
|
||||
this.shell().nativeElement.setPointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
setEditorMode(mode: 'standard' | 'select', event?: Event) {
|
||||
|
|
@ -189,7 +189,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
onShellPointerMove(event: PointerEvent) {
|
||||
if (this.selectionPointerId !== event.pointerId || !this.selectionStart) return;
|
||||
|
||||
const shellRect = this.shell.nativeElement.getBoundingClientRect();
|
||||
const shellRect = this.shell().nativeElement.getBoundingClientRect();
|
||||
const left = Math.min(this.selectionStart.x, event.clientX) - shellRect.left;
|
||||
const top = Math.min(this.selectionStart.y, event.clientY) - shellRect.top;
|
||||
const width = Math.abs(event.clientX - this.selectionStart.x);
|
||||
|
|
@ -201,7 +201,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
onShellPointerUp(event: PointerEvent) {
|
||||
if (this.selectionPointerId !== event.pointerId || !this.selectionStart) return;
|
||||
|
||||
const shell = this.shell.nativeElement;
|
||||
const shell = this.shell().nativeElement;
|
||||
if (shell.hasPointerCapture(event.pointerId)) {
|
||||
shell.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
|
|
@ -278,7 +278,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
private async reloadEditor() {
|
||||
const host = this.container?.nativeElement as HTMLElement | undefined;
|
||||
const host = this.container()?.nativeElement as HTMLElement | undefined;
|
||||
if (!host) return;
|
||||
|
||||
await this.ensureNodeTypesLoaded();
|
||||
|
|
@ -495,7 +495,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
private getDropPosition(event: DragEvent) {
|
||||
const host = this.container.nativeElement as HTMLElement;
|
||||
const host = this.container().nativeElement as HTMLElement;
|
||||
const rect = host.getBoundingClientRect();
|
||||
const transform = this.rete!.area.area.transform;
|
||||
|
||||
|
|
@ -555,7 +555,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
const selection = this.selectionBox();
|
||||
if (!selection) return [];
|
||||
|
||||
const shellRect = this.shell.nativeElement.getBoundingClientRect();
|
||||
const shellRect = this.shell().nativeElement.getBoundingClientRect();
|
||||
const selectionRect = {
|
||||
left: shellRect.left + selection.left,
|
||||
top: shellRect.top + selection.top,
|
||||
|
|
@ -564,7 +564,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
};
|
||||
|
||||
return Array.from(
|
||||
this.shell.nativeElement.querySelectorAll<HTMLElement>('[data-testid="node"][data-block-id]')
|
||||
this.shell().nativeElement.querySelectorAll<HTMLElement>('[data-testid="node"][data-block-id]')
|
||||
)
|
||||
.filter((element) => this.isRectIntersecting(selectionRect, element.getBoundingClientRect()))
|
||||
.map((element) => element.dataset['blockId'] ?? '')
|
||||
|
|
@ -585,7 +585,7 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
|
||||
private async applyZoom(multiplier: number) {
|
||||
const area = this.rete?.area?.area;
|
||||
const host = this.container?.nativeElement as HTMLElement | undefined;
|
||||
const host = this.container()?.nativeElement as HTMLElement | undefined;
|
||||
if (!area || !host) return;
|
||||
|
||||
const currentZoom = area.transform.k || 1;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
</button>
|
||||
|
||||
@if (contextAsideOpen()) {
|
||||
<aside class="w-[360px] border border-slate-200 rounded-md bg-white min-h-0 overflow-hidden flex flex-col">
|
||||
<aside class="w-90 border border-slate-200 rounded-md bg-white min-h-0 overflow-hidden flex flex-col">
|
||||
<div class="flex border-b border-slate-200 bg-slate-50 px-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, input, OnDestroy, signal, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, input, OnDestroy, signal, viewChild } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
|
@ -96,7 +96,7 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
readonly executionLogs = signal<ExecutionEventLogEntry[]>([]);
|
||||
readonly logsLoading = signal(false);
|
||||
readonly logsError = signal<string | null>(null);
|
||||
@ViewChild('logsScrollViewport') private logsScrollViewport?: ElementRef<HTMLDivElement>;
|
||||
private readonly logsScrollViewport = viewChild<ElementRef<HTMLDivElement>>('logsScrollViewport');
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
|
|
@ -821,7 +821,7 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
private scrollLogsToBottom() {
|
||||
const element = this.logsScrollViewport?.nativeElement;
|
||||
const element = this.logsScrollViewport()?.nativeElement;
|
||||
if (!element || this.activeAsideTab() !== 'logs') return;
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, signal, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, signal, viewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
|
|
@ -26,7 +26,7 @@ import { EditorStateHolder } from '@stores/flow-editor';
|
|||
export class TitleToolbar {
|
||||
private snackTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
@ViewChild('titleInput') myInputRef!: ElementRef;
|
||||
readonly titleInputRef = viewChild<ElementRef>('titleInput');
|
||||
|
||||
editorState: EditorStateHolder = inject(EditorStateHolder);
|
||||
private router = inject(Router);
|
||||
|
|
@ -98,8 +98,8 @@ export class TitleToolbar {
|
|||
this.draftTitle.set(flow.name);
|
||||
this.editingTitle.set(true);
|
||||
queueMicrotask(() => {
|
||||
this.myInputRef?.nativeElement?.focus();
|
||||
this.myInputRef?.nativeElement?.select();
|
||||
this.titleInputRef()?.nativeElement?.focus();
|
||||
this.titleInputRef()?.nativeElement?.select();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,8 @@ export class TitleToolbar {
|
|||
}
|
||||
if (trimmed.length < 4) {
|
||||
this.draftTitle.set(this.title());
|
||||
this.myInputRef.nativeElement.value = this.title();
|
||||
const inputEl = this.titleInputRef()?.nativeElement;
|
||||
if (inputEl) inputEl.value = this.title();
|
||||
this.editingTitle.set(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
export function hasValidPasswordComplexity(value: string): boolean {
|
||||
return /^\S+$/.test(value)
|
||||
&& /[a-z]/.test(value)
|
||||
&& /[A-Z]/.test(value)
|
||||
&& /\d/.test(value)
|
||||
&& /[^A-Za-z0-9]/.test(value);
|
||||
}
|
||||
|
||||
export type PasswordCheck = { label: string; satisfied: boolean };
|
||||
|
||||
export function evaluatePasswordChecks(password: string): PasswordCheck[] {
|
||||
return [
|
||||
{ label: 'At least 8 characters', satisfied: password.length >= 8 },
|
||||
{ label: 'At least one lowercase letter', satisfied: /[a-z]/.test(password) },
|
||||
{ label: 'At least one uppercase letter', satisfied: /[A-Z]/.test(password) },
|
||||
{ label: 'At least one number', satisfied: /\d/.test(password) },
|
||||
{ label: 'At least one special character', satisfied: /[^A-Za-z0-9]/.test(password) },
|
||||
{ label: 'No spaces', satisfied: /^\S*$/.test(password) }
|
||||
];
|
||||
}
|
||||
|
||||
export function initialPasswordChecks(): PasswordCheck[] {
|
||||
return [
|
||||
{ label: 'At least 8 characters', satisfied: false },
|
||||
{ label: 'At least one lowercase letter', satisfied: false },
|
||||
{ label: 'At least one uppercase letter', satisfied: false },
|
||||
{ label: 'At least one number', satisfied: false },
|
||||
{ label: 'At least one special character', satisfied: false },
|
||||
{ label: 'No spaces', satisfied: true }
|
||||
];
|
||||
}
|
||||
Loading…
Reference in New Issue