diff --git a/src/app/shared/task-execution-viewer/task-execution-viewer.css b/src/app/shared/task-execution-viewer/task-execution-viewer.css index a898909..2ca77df 100644 --- a/src/app/shared/task-execution-viewer/task-execution-viewer.css +++ b/src/app/shared/task-execution-viewer/task-execution-viewer.css @@ -1013,3 +1013,66 @@ align-items: center; gap: 4px; } +/* Credential setup uses the same neutral surface and compact controls as the execution view. */ +.execution-credential-banner, +.execution-credential-card { + border: 1px solid #dbe3ee; + border-radius: .5rem; + background: #fff; +} + +.execution-credential-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: .75rem; + margin: .25rem; + padding: .7rem .9rem; + border-left: 3px solid #3b82f6; +} + +.execution-credential-banner__title { + display: flex; + align-items: center; + gap: .375rem; + color: #334155; + font-size: .8rem; + font-weight: 700; +} +.execution-credential-banner__message { margin-top: .125rem; font-size: .75rem; color: #64748b; } + +.execution-credential-card { + margin: .75rem; + padding: .8rem; +} +.execution-credential-card--provided { + display: flex; + align-items: center; + justify-content: space-between; + gap: .75rem; + border-left: 3px solid #22c55e; +} + +.execution-credential-card__title { color: #334155; font-size: .8125rem; font-weight: 700; } +.execution-credential-button { + border: 1px solid transparent; + border-radius: .375rem; + padding: .45rem .7rem; + font-size: .75rem; + font-weight: 600; + cursor: pointer; +} + +.execution-credential-button--primary { background: #2563eb; color: #fff; } +.execution-credential-button--secondary { border-color: #cbd5e1; background: #fff; color: #334155; } +.execution-credential-button:disabled { opacity: .55; } + +.execution-credential-card__error { + margin-top: .6rem; + padding: .5rem .625rem; + border: 1px solid #fecaca; + border-radius: .375rem; + background: #fef2f2; + color: #b91c1c; + font-size: .75rem; +} diff --git a/src/app/shared/task-execution-viewer/task-execution-viewer.html b/src/app/shared/task-execution-viewer/task-execution-viewer.html index a0a0698..b15e1bb 100644 --- a/src/app/shared/task-execution-viewer/task-execution-viewer.html +++ b/src/app/shared/task-execution-viewer/task-execution-viewer.html @@ -124,16 +124,24 @@ } @if (authorizationGate().missingProviders.length || authorizationGate().runtime.length) { -
- +
+
+
+ + Authorization required +
+
@if (authorizationGate().missingProviders.length) { This execution needs a Vault credential for {{ authorizationGate().missingProviders.join(', ') }} before it can start. } @else { This execution needs a provider authorization before it can start. } - - -
+
+
+ + } @if (outcomes().length) {
@@ -343,58 +351,64 @@ } @for (entry of settledVaultAuthorizations(); track entry.requirement.key) { -
+
-
{{ entry.provider }} credential
-
{{ providedCredentialLabel(entry) }}
+
{{ entry.provider }} credential
+
{{ providedCredentialLabel(entry) }}
- +
} @for (entry of pendingVaultAuthorizations(); track entry.requirement.key) { -
-
Vault credential for {{ entry.provider }}
-
Select an active credential to start the execution.
+
+
Vault credential for {{ entry.provider }}
+
Select an active credential to start the execution.
@if (entry.requiresCredential === false) { -
This provider is listed as not requiring a credential, but the execution is asking for one.
+
This provider is listed as not requiring a credential, but the execution is asking for one.
} - - Credential - Loading saved credentials…
+ } @else if (llmCredentialOptionsFor(entry).length) { + + @if (authorizationSavingFor(entry)) {
Saving credential…
} + } @else { +
No saved {{ entry.provider }} credentials. Add one to continue.
+ } @if (llmCredentialErrorFor(entry); as credentialError) { -
+
{{ credentialError }} - +
} @if (authorizationErrorFor(entry); as authorizationError) { -
{{ authorizationError }}
+
{{ authorizationError }}
} @if (credentialFormError(); as formError) { -
{{ formError }}
+
{{ formError }}
} -
+
@if (!llmCredentialLoadingFor(entry)) { - } @if (editingAuthorizationKeys()[entry.requirement.key]) { - + }
diff --git a/src/app/shared/task-execution-viewer/task-execution-viewer.ts b/src/app/shared/task-execution-viewer/task-execution-viewer.ts index 9eb4edc..1b1d718 100644 --- a/src/app/shared/task-execution-viewer/task-execution-viewer.ts +++ b/src/app/shared/task-execution-viewer/task-execution-viewer.ts @@ -10,8 +10,6 @@ import { NotificationService } from '@services/notifications/notification'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; import { areFlowValueKindsCompatible, FlowBlock, @@ -143,7 +141,7 @@ function uploadFailureMessage(failure: unknown): string { @Component({ selector: 'app-task-execution-viewer', - imports: [CommonModule, FormsModule, ReteEditor, TaskExecutionInputsPanelComponent, MatButtonModule, MatIconModule, MatTooltipModule, MatFormFieldModule, MatSelectModule, BiasImpactReportListComponent, JsonViewerComponent], + imports: [CommonModule, FormsModule, ReteEditor, TaskExecutionInputsPanelComponent, MatButtonModule, MatIconModule, MatTooltipModule, BiasImpactReportListComponent, JsonViewerComponent], templateUrl: './task-execution-viewer.html', styleUrl: './task-execution-viewer.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -966,6 +964,29 @@ export class TaskExecutionViewerComponent implements OnDestroy { this.activeAsideTab.set('inputs'); } + /** The banner creates the first credential directly: there is nothing to choose from yet. */ + openAuthorizationAction() { + const pending = this.pendingVaultAuthorizations(); + if (pending.length === 1 + && !this.llmCredentialLoadingFor(pending[0]) + && !this.llmCredentialOptionsFor(pending[0]).length) { + this.openAuthorizationPanel(); + void this.openVaultCredentialForm(pending[0].provider); + return; + } + this.openAuthorizationPanel(); + } + + authorizationActionLabel(): string { + const pending = this.pendingVaultAuthorizations(); + if (pending.length === 1 + && !this.llmCredentialLoadingFor(pending[0]) + && !this.llmCredentialOptionsFor(pending[0]).length) { + return 'Add credential'; + } + return pending.length ? 'Choose credential' : 'Open authorization'; + } + toggleContextAside() { this.contextAsideOpen.update((open) => !open); }