Fix scan submit: imageRef signal binding + API URL path

Bug: canSubmit computed() used plain string imageRef which computed()
can't track. Submit button was permanently disabled.
Fix: Changed imageRef to signal(''), updated template to use
[ngModel]="imageRef()" (ngModelChange)="imageRef.set($event)",
updated all class references to imageRef() for reads and .set() for writes.

Bug: Scan submit called /scanner/api/v1/scans/ (console nginx prefix)
instead of /api/v1/scans/ (gateway route).
Fix: Removed /scanner/ prefix from all API URLs.

KNOWN ISSUE: Scanner service needs identity envelope middleware (same
as Concelier fix) — ReverseProxy strips JWT, scanner returns 400
"tenant required". The scan submission reaches the scanner but auth
fails. Fix requires adding the same pre-auth envelope middleware
pattern to the Scanner service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-16 17:55:58 +02:00
parent 6660676341
commit 1acc87a25d

View File

@@ -57,7 +57,8 @@ interface ScanStatusResponse {
Image reference *
<input
type="text"
[(ngModel)]="imageRef"
[ngModel]="imageRef()"
(ngModelChange)="imageRef.set($event)"
placeholder="e.g., registry.stella-ops.local/myapp:v1.2.3"
/>
</label>
@@ -160,7 +161,7 @@ interface ScanStatusResponse {
</div>
<div class="detail-row">
<span class="detail-label">Image</span>
<span class="detail-value">{{ imageRef }}</span>
<span class="detail-value">{{ imageRef() }}</span>
</div>
<div class="detail-row">
<span class="detail-label">Status</span>
@@ -471,7 +472,7 @@ export class ScanSubmitComponent implements OnDestroy {
private readonly destroyRef = inject(DestroyRef);
/** Form state */
imageRef = '';
imageRef = signal('');
forceRescan = false;
showMetadata = false;
metadataEntries: MetadataEntry[] = [];
@@ -487,7 +488,7 @@ export class ScanSubmitComponent implements OnDestroy {
/** Derive image name (strip tag/digest for triage link) */
readonly imageName = computed(() => {
const ref = this.imageRef.trim();
const ref = this.imageRef().trim();
const atIdx = ref.indexOf('@');
if (atIdx > 0) return ref.substring(0, atIdx);
const colonIdx = ref.lastIndexOf(':');
@@ -496,7 +497,7 @@ export class ScanSubmitComponent implements OnDestroy {
});
readonly canSubmit = computed(() => {
return this.imageRef.trim().length > 0 && !this.submitting();
return this.imageRef().trim().length > 0 && !this.submitting();
});
addMetadataEntry(): void {
@@ -523,7 +524,7 @@ export class ScanSubmitComponent implements OnDestroy {
}
const body: Record<string, unknown> = {
image: { reference: this.imageRef.trim() },
image: { reference: this.imageRef().trim() },
force: this.forceRescan,
};
@@ -531,7 +532,7 @@ export class ScanSubmitComponent implements OnDestroy {
body['metadata'] = metadata;
}
this.http.post<ScanSubmitResponse>('/scanner/api/v1/scans/', body).pipe(
this.http.post<ScanSubmitResponse>('/api/v1/scans/', body).pipe(
takeUntilDestroyed(this.destroyRef),
).subscribe({
next: (response) => {
@@ -552,7 +553,7 @@ export class ScanSubmitComponent implements OnDestroy {
this.scanId.set(null);
this.scanStatus.set('queued');
this.submitError.set(null);
this.imageRef = '';
this.imageRef.set('');
this.forceRescan = false;
this.showMetadata = false;
this.metadataEntries = [];
@@ -567,7 +568,7 @@ export class ScanSubmitComponent implements OnDestroy {
this.pollSubscription = timer(0, 3000).pipe(
switchMap(() =>
this.http.get<ScanStatusResponse>(`/scanner/api/v1/scans/${encodeURIComponent(scanId)}`)
this.http.get<ScanStatusResponse>(`/api/v1/scans/${encodeURIComponent(scanId)}`)
),
tap((response) => {
this.scanStatus.set(response.status);