Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -519,15 +519,15 @@
}

.example-trigger__icon-box--custom {
background: color-mix(in srgb, #c084fc, transparent 80%);
color: #6d28d9;
background: color-mix(in srgb, var(--alternative-color), transparent 80%);
color: var(--alternative-color);
}

@media (prefers-color-scheme: dark) {
.example-trigger__icon-box--add { background: color-mix(in srgb, var(--success-color), transparent 88%); color: var(--success-color); }
.example-trigger__icon-box--update { background: color-mix(in srgb, var(--info-color), transparent 88%); color: var(--info-color); }
.example-trigger__icon-box--delete { background: color-mix(in srgb, var(--error-color), transparent 88%); color: var(--error-color); }
.example-trigger__icon-box--custom { background: rgba(192, 132, 252, 0.12); color: #c084fc; }
.example-trigger__icon-box--custom { background: color-mix(in srgb, var(--alternative-color), transparent 88%); color: var(--alternative-color); }
}

.example-trigger__text {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@
.form-parse-warning {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
gap: 16px;
padding: 12px 16px;
border: 1px solid var(--warning-color);
margin-bottom: 12px;
border-radius: 4px;
background: var(--mdc-theme-warning-container, #fff3e0);
color: var(--mdc-theme-on-warning-container, #e65100);
background: var(--warning-background-color);
color: var(--warning-color);
font-size: 13px;
}

.form-parse-warning mat-icon {
flex-shrink: 0;
}

.dialog-actions-spacer {
flex: 1;
}

.code-editor-box {
height: 300px;
border: 1px solid var(--mdc-outlined-text-field-outline-color, rgba(0, 0, 0, 0.38));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
<h1 mat-dialog-title>Policy — {{ data.groupTitle }}</h1>
<mat-dialog-content>
<div class="editor-mode-toggle">
<mat-button-toggle-group [value]="editorMode" (change)="onEditorModeChange($event.value)">
<mat-button-toggle value="form">Form</mat-button-toggle>
<mat-button-toggle value="code">Code</mat-button-toggle>
</mat-button-toggle-group>
</div>
<form (ngSubmit)="savePolicy()">
<mat-dialog-content #dialogContent>
<div class="editor-mode-toggle">
<mat-button-toggle-group [value]="editorMode" (change)="onEditorModeChange($event.value)">
<mat-button-toggle value="form">Form</mat-button-toggle>
<mat-button-toggle value="code">Code</mat-button-toggle>
</mat-button-toggle-group>
</div>

<div *ngIf="formParseError" class="form-parse-warning">
<mat-icon>warning</mat-icon>
<span>This policy uses advanced Cedar syntax that cannot be represented in form mode. Please use the code editor.</span>
</div>
<div *ngIf="formParseError" class="form-parse-warning">
<mat-icon>warning</mat-icon>
<span>This policy uses advanced Cedar syntax that cannot be represented in form mode. Please use the code editor.</span>
</div>

<div *ngIf="editorMode === 'form' && !formParseError">
<app-cedar-policy-list
[policies]="policyItems"
[availableTables]="availableTables"
[availableDashboards]="availableDashboards"
[loading]="loading"
(policiesChange)="onPolicyItemsChange($event)">
</app-cedar-policy-list>
</div>
<div *ngIf="editorMode === 'form' && !formParseError">
<app-cedar-policy-list
[policies]="policyItems"
[availableTables]="availableTables"
[availableDashboards]="availableDashboards"
[loading]="loading"
(policiesChange)="onPolicyItemsChange($event)">
</app-cedar-policy-list>
</div>

<div *ngIf="editorMode === 'code'">
<p class="cedar-hint">Edit policy in <a href="https://www.cedarpolicy.com/en" target="_blank" rel="noopener">Cedar</a> format</p>
<div class="code-editor-box">
<ngs-code-editor
[theme]="codeEditorTheme"
[codeModel]="cedarPolicyModel"
[options]="codeEditorOptions"
(valueChanged)="onCedarPolicyChange($event)">
</ngs-code-editor>
<div *ngIf="editorMode === 'code'">
<p class="cedar-hint">Edit policy in <a href="https://www.cedarpolicy.com/en" target="_blank" rel="noopener">Cedar</a> format</p>
<div class="code-editor-box">
<ngs-code-editor
[theme]="codeEditorTheme"
[codeModel]="cedarPolicyModel"
[options]="codeEditorOptions"
(valueChanged)="onCedarPolicyChange($event)">
</ngs-code-editor>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button type="button" mat-flat-button mat-dialog-close>Cancel</button>
<button type="button" mat-flat-button color="primary"
[disabled]="submitting"
(click)="savePolicy()">
Save
</button>
</mat-dialog-actions>
</mat-dialog-content>
<mat-dialog-actions>
<button *ngIf="editorMode === 'form' && !formParseError && !loading && !policyList?.showAddForm"
type="button" mat-button color="primary"
(click)="onAddPolicyClick()">
<mat-icon>add</mat-icon> Add policy
</button>
<span class="dialog-actions-spacer"></span>
<button type="button" mat-flat-button (click)="confirmClose()">Cancel</button>
<button type="submit" mat-flat-button color="primary"
[disabled]="submitting">
Save
</button>
</mat-dialog-actions>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ describe('CedarPolicyEditorDialogComponent', () => {

const mockDialogRef = {
close: () => {},
backdropClick: () => of(),
keydownEvents: () => of(),
disableClose: false,
};

const fakeTables = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NgIf } from '@angular/common';
import { Component, DestroyRef, Inject, inject, OnInit } from '@angular/core';
import { Component, DestroyRef, ElementRef, Inject, inject, OnInit, ViewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
Expand Down Expand Up @@ -36,6 +37,7 @@ export interface CedarPolicyEditorDialogData {
styleUrls: ['./cedar-policy-editor-dialog.component.css'],
imports: [
NgIf,
FormsModule,
MatDialogModule,
MatButtonModule,
MatButtonToggleModule,
Expand All @@ -57,6 +59,9 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
public loading: boolean = true;
public formParseError: boolean = false;

@ViewChild(CedarPolicyListComponent) policyList?: CedarPolicyListComponent;
@ViewChild('dialogContent', { read: ElementRef }) dialogContent?: ElementRef<HTMLElement>;

public cedarPolicyModel: object;
public codeEditorOptions = {
minimap: { enabled: false },
Expand All @@ -80,6 +85,16 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
) {
this.codeEditorTheme = this._uiSettings.isDarkMode ? 'vs-dark' : 'vs';
this._editorService.loaded.pipe(take(1)).subscribe(({ monaco }) => registerCedarLanguage(monaco));

this.dialogRef.disableClose = true;
this.dialogRef.backdropClick().pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => {
this.confirmClose();
});
this.dialogRef.keydownEvents().pipe(takeUntilDestroyed(this._destroyRef)).subscribe((event) => {
if (event.key === 'Escape') {
this.confirmClose();
}
});
}

ngOnInit(): void {
Expand Down Expand Up @@ -155,7 +170,22 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
this.editorMode = mode;
}

confirmClose() {
if (this.editorMode === 'form' && this.policyList?.hasPendingChanges()) {
const discard = confirm('You have an unsaved policy in the form. Discard it and close?');
if (!discard) return;
this.policyList.discardPending();
}
this.dialogRef.close();
}

savePolicy() {
if (this.editorMode === 'form' && this.policyList?.hasPendingChanges()) {
const discard = confirm('You have an unsaved policy in the form. Discard it and save?');
if (!discard) return;
this.policyList.discardPending();
}
Comment on lines 182 to +187
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using window.confirm() here is inconsistent with the rest of the app’s confirmation flows (which use MatDialog confirmation dialogs) and is not themeable/test-friendly. Consider replacing this with a Material dialog-based confirmation, and ensure the message is localized consistently with the rest of the UI.

Copilot uses AI. Check for mistakes.

this.submitting = true;

if (this.editorMode === 'form') {
Expand All @@ -182,6 +212,17 @@ export class CedarPolicyEditorDialogComponent implements OnInit {
});
}

onAddPolicyClick() {
if (!this.policyList) return;
this.policyList.showAddForm = true;
setTimeout(() => {
const el = this.dialogContent?.nativeElement;
if (el) {
el.scrollTop = el.scrollHeight;
}
});
}

private _parseCedarToPolicyItems(): CedarPolicyItem[] {
const parsed = parseCedarPolicy(this.cedarPolicy, this.connectionID, this.data.groupId, this.allTables);
const dashboardItems = parseCedarDashboardItems(this.cedarPolicy, this.connectionID);
Expand Down
Loading
Loading