gcp-material-ui 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/charts/SKILL.MD +103 -0
- package/.agents/skills/creating-common-components/SKILL.MD +121 -0
- package/.agents/skills/creating-pages/SKILL.MD +205 -0
- package/.agents/skills/sidebar/SKILL.MD +63 -0
- package/.agents/skills/workflow-graph/SKILL.MD +160 -0
- package/.dockerignore +4 -0
- package/.editorconfig +17 -0
- package/.prettierrc +12 -0
- package/Dockerfile +14 -0
- package/PUBLISH_GUIDE.md +33 -0
- package/README.md +48 -0
- package/angular.json +79 -0
- package/deploy.sh +47 -0
- package/nginx.conf +15 -0
- package/package.json +48 -0
- package/public/favicon.ico +0 -0
- package/public/google-cloud-combined.svg +1 -0
- package/publish.sh +14 -0
- package/src/_variables.scss +183 -0
- package/src/app/app.config.ts +19 -0
- package/src/app/app.html +1 -0
- package/src/app/app.routes.ts +65 -0
- package/src/app/app.scss +0 -0
- package/src/app/app.spec.ts +23 -0
- package/src/app/app.ts +12 -0
- package/src/app/auth/auth.guard.ts +7 -0
- package/src/app/auth/auth.service.ts +50 -0
- package/src/app/components/page/page.html +20 -0
- package/src/app/components/page/page.scss +41 -0
- package/src/app/components/page/page.ts +15 -0
- package/src/app/dashboard/dashboard.html +287 -0
- package/src/app/dashboard/dashboard.scss +196 -0
- package/src/app/dashboard/dashboard.ts +24 -0
- package/src/app/demo-components/buttons/buttons-demo.html +92 -0
- package/src/app/demo-components/buttons/buttons-demo.scss +38 -0
- package/src/app/demo-components/buttons/buttons-demo.ts +23 -0
- package/src/app/demo-components/card/card-demo.html +52 -0
- package/src/app/demo-components/card/card-demo.scss +47 -0
- package/src/app/demo-components/card/card-demo.ts +21 -0
- package/src/app/demo-components/charts/charts-demo.html +88 -0
- package/src/app/demo-components/charts/charts-demo.scss +50 -0
- package/src/app/demo-components/charts/charts-demo.ts +201 -0
- package/src/app/demo-components/chips/chips-demo.html +87 -0
- package/src/app/demo-components/chips/chips-demo.scss +6 -0
- package/src/app/demo-components/chips/chips-demo.ts +57 -0
- package/src/app/demo-components/code-snippet/code-snippet-demo.html +52 -0
- package/src/app/demo-components/code-snippet/code-snippet-demo.scss +20 -0
- package/src/app/demo-components/code-snippet/code-snippet-demo.ts +31 -0
- package/src/app/demo-components/dialog/dialog-demo.html +47 -0
- package/src/app/demo-components/dialog/dialog-demo.ts +90 -0
- package/src/app/demo-components/dialog/dialog-examples.ts +126 -0
- package/src/app/demo-components/expand-button/expand-button-demo.html +28 -0
- package/src/app/demo-components/expand-button/expand-button-demo.scss +45 -0
- package/src/app/demo-components/expand-button/expand-button-demo.ts +28 -0
- package/src/app/demo-components/expansion/expansion-demo.html +105 -0
- package/src/app/demo-components/expansion/expansion-demo.scss +11 -0
- package/src/app/demo-components/expansion/expansion-demo.ts +49 -0
- package/src/app/demo-components/forms/forms-demo.html +114 -0
- package/src/app/demo-components/forms/forms-demo.scss +43 -0
- package/src/app/demo-components/forms/forms-demo.ts +57 -0
- package/src/app/demo-components/message/message-demo.html +47 -0
- package/src/app/demo-components/message/message-demo.ts +33 -0
- package/src/app/demo-components/message/message.html +15 -0
- package/src/app/demo-components/message/message.scss +115 -0
- package/src/app/demo-components/message/message.ts +39 -0
- package/src/app/demo-components/sidebar/sidebar-demo.html +46 -0
- package/src/app/demo-components/sidebar/sidebar-demo.scss +47 -0
- package/src/app/demo-components/sidebar/sidebar-demo.ts +27 -0
- package/src/app/demo-components/sidebar/sidebar.html +15 -0
- package/src/app/demo-components/sidebar/sidebar.scss +25 -0
- package/src/app/demo-components/sidebar/sidebar.ts +87 -0
- package/src/app/demo-components/slide-toggle/slide-toggle-demo.html +110 -0
- package/src/app/demo-components/slide-toggle/slide-toggle-demo.scss +45 -0
- package/src/app/demo-components/slide-toggle/slide-toggle-demo.ts +39 -0
- package/src/app/demo-components/stepper/stepper-demo.html +99 -0
- package/src/app/demo-components/stepper/stepper-demo.ts +84 -0
- package/src/app/demo-components/tables/clean_tables_scss.py +16 -0
- package/src/app/demo-components/tables/tables-demo.html +64 -0
- package/src/app/demo-components/tables/tables-demo.scss +18 -0
- package/src/app/demo-components/tables/tables-demo.ts +89 -0
- package/src/app/demo-components/tabs/tabs-demo.html +168 -0
- package/src/app/demo-components/tabs/tabs-demo.scss +22 -0
- package/src/app/demo-components/tabs/tabs-demo.ts +76 -0
- package/src/app/demo-components/toolbars/toolbars-demo.html +50 -0
- package/src/app/demo-components/toolbars/toolbars-demo.scss +29 -0
- package/src/app/demo-components/toolbars/toolbars-demo.ts +25 -0
- package/src/app/demo-components/tooltip/tooltip-demo.html +80 -0
- package/src/app/demo-components/tooltip/tooltip-demo.scss +30 -0
- package/src/app/demo-components/tooltip/tooltip-demo.ts +27 -0
- package/src/app/demo-components/typography/typography-demo.html +23 -0
- package/src/app/demo-components/typography/typography-demo.scss +49 -0
- package/src/app/demo-components/typography/typography-demo.ts +72 -0
- package/src/app/demo-components/workflow-graph/workflow-graph-demo.html +25 -0
- package/src/app/demo-components/workflow-graph/workflow-graph-demo.scss +48 -0
- package/src/app/demo-components/workflow-graph/workflow-graph-demo.ts +133 -0
- package/src/app/layout/layout.html +12 -0
- package/src/app/layout/layout.scss +21 -0
- package/src/app/layout/layout.ts +19 -0
- package/src/app/layout/side-menu/side-menu.html +32 -0
- package/src/app/layout/side-menu/side-menu.scss +52 -0
- package/src/app/layout/side-menu/side-menu.ts +70 -0
- package/src/app/layout/top-action-bar/top-action-bar.html +41 -0
- package/src/app/layout/top-action-bar/top-action-bar.scss +98 -0
- package/src/app/layout/top-action-bar/top-action-bar.ts +68 -0
- package/src/app/page-templates/complex-form/complex-form.html +103 -0
- package/src/app/page-templates/complex-form/complex-form.scss +110 -0
- package/src/app/page-templates/complex-form/complex-form.ts +66 -0
- package/src/app/page-templates/firewall-add/firewall-add.html +78 -0
- package/src/app/page-templates/firewall-add/firewall-add.scss +1 -0
- package/src/app/page-templates/firewall-add/firewall-add.ts +94 -0
- package/src/app/page-templates/firewall-details/firewall-details.html +115 -0
- package/src/app/page-templates/firewall-details/firewall-details.scss +21 -0
- package/src/app/page-templates/firewall-details/firewall-details.ts +91 -0
- package/src/app/page-templates/firewall-list/firewall-list.html +99 -0
- package/src/app/page-templates/firewall-list/firewall-list.scss +21 -0
- package/src/app/page-templates/firewall-list/firewall-list.ts +85 -0
- package/src/app/page-templates/firewall.service.ts +84 -0
- package/src/app/services/project.service.ts +24 -0
- package/src/index.html +17 -0
- package/src/main.ts +6 -0
- package/src/styles.scss +739 -0
- package/start_server.sh +4 -0
- package/tsconfig.app.json +15 -0
- package/tsconfig.json +33 -0
- package/tsconfig.spec.json +15 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.toolbar {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
position: sticky;
|
|
5
|
+
top: 0;
|
|
6
|
+
z-index: 1000;
|
|
7
|
+
border-bottom: 1px solid #e8eaed;
|
|
8
|
+
padding: 0;
|
|
9
|
+
}
|
|
10
|
+
.logo {
|
|
11
|
+
height: 20px;
|
|
12
|
+
margin-left: 8px;
|
|
13
|
+
margin-top: 3px;
|
|
14
|
+
}
|
|
15
|
+
.spacer {
|
|
16
|
+
flex: 1 1 auto;
|
|
17
|
+
}
|
|
18
|
+
.user-avatar {
|
|
19
|
+
width: 32px;
|
|
20
|
+
height: 32px;
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.user-popover-header {
|
|
26
|
+
padding: 16px;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: 4px;
|
|
30
|
+
border-bottom: 1px solid #e8eaed;
|
|
31
|
+
background: #f8f9fa;
|
|
32
|
+
min-width: 200px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.user-name {
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
color: #1a1a1a;
|
|
39
|
+
line-height: 1.2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.user-email {
|
|
43
|
+
font-size: 12px;
|
|
44
|
+
color: #5f6368;
|
|
45
|
+
line-height: 1.2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.project-selector-container {
|
|
49
|
+
margin-left: 24px;
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.project-picker-btn {
|
|
55
|
+
height: 36px;
|
|
56
|
+
border-radius: 4px;
|
|
57
|
+
border: 1px solid #909090 !important; /* using important if material overrides it */
|
|
58
|
+
box-shadow: none !important;
|
|
59
|
+
background: white;
|
|
60
|
+
color: #5f6368;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
padding: 0 12px;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
font-family: inherit;
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.picker-icon {
|
|
70
|
+
font-size: 18px;
|
|
71
|
+
width: 18px;
|
|
72
|
+
height: 18px;
|
|
73
|
+
color: #444746;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.project-name {
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
max-width: 250px;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
text-overflow: ellipsis;
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.arrow-icon {
|
|
85
|
+
font-size: 18px;
|
|
86
|
+
width: 18px;
|
|
87
|
+
height: 18px;
|
|
88
|
+
color: #444746;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.user-button {
|
|
92
|
+
padding: 4px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
::ng-deep .project-menu, ::ng-deep .user-menu-popover {
|
|
96
|
+
background-color: white !important;
|
|
97
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
98
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Component, EventEmitter, Output, inject } from '@angular/core';
|
|
2
|
+
import { AuthService } from '../../auth/auth.service';
|
|
3
|
+
import { ProjectService, Project } from '../../services/project.service';
|
|
4
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
5
|
+
import { signal, OnInit } from '@angular/core';
|
|
6
|
+
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
7
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
8
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
9
|
+
import { MatMenuModule } from '@angular/material/menu';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'app-top-action-bar',
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [MatToolbarModule, MatButtonModule, MatIconModule, MatMenuModule],
|
|
15
|
+
templateUrl: './top-action-bar.html',
|
|
16
|
+
styleUrl: './top-action-bar.scss'
|
|
17
|
+
})
|
|
18
|
+
export class TopActionBarComponent implements OnInit {
|
|
19
|
+
@Output() toggleSidenav = new EventEmitter<void>();
|
|
20
|
+
public authService = inject(AuthService);
|
|
21
|
+
private projectService = inject(ProjectService);
|
|
22
|
+
private router = inject(Router);
|
|
23
|
+
private route = inject(ActivatedRoute);
|
|
24
|
+
|
|
25
|
+
projects = signal<Project[]>([]);
|
|
26
|
+
selectedProjectId = signal<string | null>(null);
|
|
27
|
+
|
|
28
|
+
ngOnInit() {
|
|
29
|
+
this.authService.isReady().then(() => {
|
|
30
|
+
setTimeout(() => this.loadProjects());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.route.queryParams.subscribe(params => {
|
|
34
|
+
const p = params['project'];
|
|
35
|
+
if (p) {
|
|
36
|
+
setTimeout(() => this.selectedProjectId.set(p));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
loadProjects() {
|
|
42
|
+
this.projectService.getProjects().subscribe({
|
|
43
|
+
next: (projects) => {
|
|
44
|
+
this.projects.set(projects);
|
|
45
|
+
},
|
|
46
|
+
error: (err) => {
|
|
47
|
+
console.error('Failed to load projects:', err);
|
|
48
|
+
this.projects.set([]);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
selectProject(projectId: string) {
|
|
54
|
+
this.router.navigate([], {
|
|
55
|
+
relativeTo: this.route,
|
|
56
|
+
queryParams: { project: projectId },
|
|
57
|
+
queryParamsHandling: 'merge'
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
navigateToHome() {
|
|
62
|
+
this.router.navigate(['/'], { queryParamsHandling: 'preserve' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
logout() {
|
|
66
|
+
this.authService.logout();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<app-page title="Complex Form Example">
|
|
2
|
+
<div class="default-container-padding">
|
|
3
|
+
<mat-vertical-stepper class="custom-stepper" [linear]="true" #stepper>
|
|
4
|
+
|
|
5
|
+
<!-- Step 1: Configure policy -->
|
|
6
|
+
<mat-step [stepControl]="step1FormGroup">
|
|
7
|
+
<form [formGroup]="step1FormGroup" class="step-content">
|
|
8
|
+
<ng-template matStepLabel>Configure policy</ng-template>
|
|
9
|
+
|
|
10
|
+
<div class="row">
|
|
11
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
12
|
+
<mat-label>Policy name</mat-label>
|
|
13
|
+
<input matInput formControlName="policyName" placeholder="Lowercase letters, numbers, hyphens allowed">
|
|
14
|
+
|
|
15
|
+
<mat-hint>Lowercase letters, numbers, hyphens allowed</mat-hint>
|
|
16
|
+
<mat-error *ngIf="step1FormGroup.get('policyName')?.hasError('required')">Policy name is required.</mat-error>
|
|
17
|
+
<mat-error *ngIf="step1FormGroup.get('policyName')?.hasError('pattern')">Invalid format (letters, numbers, hyphens only).</mat-error>
|
|
18
|
+
</mat-form-field>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="row">
|
|
22
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
23
|
+
<mat-label>Description</mat-label>
|
|
24
|
+
<textarea matInput formControlName="description" rows="3" placeholder="Description"></textarea>
|
|
25
|
+
</mat-form-field>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="row">
|
|
29
|
+
<div class="form-section-header">
|
|
30
|
+
<span class="section-title">Policy type</span>
|
|
31
|
+
<p class="section-description">Select the policy type to define the network to which this policy applies, and the firewall features it enables. <a href="javascript:void(0)" class="learn-more">Learn more <span class="material-icons" style="font-size: 14px; vertical-align: middle;">open_in_new</span></a></p>
|
|
32
|
+
</div>
|
|
33
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
34
|
+
<mat-label>Policy type</mat-label>
|
|
35
|
+
<mat-select formControlName="policyType">
|
|
36
|
+
|
|
37
|
+
<mat-option *ngFor="let type of policyTypes" [value]="type.value">{{ type.label }}</mat-option>
|
|
38
|
+
</mat-select>
|
|
39
|
+
</mat-form-field>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="row direction-col">
|
|
43
|
+
<div class="form-section-header">
|
|
44
|
+
<span class="section-title">Deployment scope</span>
|
|
45
|
+
<p class="section-description">Use global network firewall policies to configure firewall policy rules with Layer 7 inspection. <a href="javascript:void(0)" class="learn-more">Learn more <span class="material-icons" style="font-size: 14px; vertical-align: middle;">open_in_new</span></a></p>
|
|
46
|
+
</div>
|
|
47
|
+
<mat-radio-group formControlName="scope" class="vertical-radio-group">
|
|
48
|
+
<mat-radio-button value="global">Global</mat-radio-button>
|
|
49
|
+
<mat-radio-button value="regional">Regional</mat-radio-button>
|
|
50
|
+
</mat-radio-group>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="stepper-actions">
|
|
54
|
+
<button mat-stroked-button color="primary" matStepperNext>Continue</button>
|
|
55
|
+
</div>
|
|
56
|
+
</form>
|
|
57
|
+
</mat-step>
|
|
58
|
+
|
|
59
|
+
<!-- Step 2: Add rules -->
|
|
60
|
+
<mat-step [stepControl]="step2FormGroup">
|
|
61
|
+
<ng-template matStepLabel>Add rules</ng-template>
|
|
62
|
+
<div class="step-content">
|
|
63
|
+
<p>Define your firewall rules here.</p>
|
|
64
|
+
<div class="stepper-actions">
|
|
65
|
+
<button mat-button matStepperPrevious>Back</button>
|
|
66
|
+
<button mat-flat-button color="primary" matStepperNext>Continue</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</mat-step>
|
|
70
|
+
|
|
71
|
+
<!-- Step 3: Add mirroring rules -->
|
|
72
|
+
<mat-step [stepControl]="step3FormGroup">
|
|
73
|
+
<ng-template matStepLabel>Add mirroring rules</ng-template>
|
|
74
|
+
<div class="step-content">
|
|
75
|
+
<p>Define mirroring rules here.</p>
|
|
76
|
+
<div class="stepper-actions">
|
|
77
|
+
<button mat-button matStepperPrevious>Back</button>
|
|
78
|
+
<button mat-flat-button color="primary" matStepperNext>Continue</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</mat-step>
|
|
82
|
+
|
|
83
|
+
<!-- Step 4: Associate policy with networks -->
|
|
84
|
+
<mat-step [stepControl]="step4FormGroup">
|
|
85
|
+
<ng-template matStepLabel>Associate policy with networks (optional)</ng-template>
|
|
86
|
+
<div class="step-content">
|
|
87
|
+
<p>Associate networks here.</p>
|
|
88
|
+
<div class="stepper-actions">
|
|
89
|
+
<button mat-button matStepperPrevious>Back</button>
|
|
90
|
+
<button mat-flat-button color="primary" matStepperNext>Continue</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</mat-step>
|
|
94
|
+
|
|
95
|
+
</mat-vertical-stepper>
|
|
96
|
+
|
|
97
|
+
<!-- Global Actions -->
|
|
98
|
+
<div class="global-actions">
|
|
99
|
+
<button mat-flat-button color="primary" (click)="onSubmit()" [disabled]="step1FormGroup.invalid">Create</button>
|
|
100
|
+
<button mat-button (click)="cancel()">Cancel</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</app-page>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.custom-stepper {
|
|
2
|
+
background-color: transparent; // Make it blend with background
|
|
3
|
+
box-shadow: none;
|
|
4
|
+
|
|
5
|
+
.mat-step-header {
|
|
6
|
+
background-color: transparent;
|
|
7
|
+
padding-left: 0;
|
|
8
|
+
padding-right: 0;
|
|
9
|
+
|
|
10
|
+
.mat-step-icon {
|
|
11
|
+
background-color: #5f6368; // Default grey context
|
|
12
|
+
color: #fff;
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
font-weight: 500;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.mat-step-icon-selected {
|
|
18
|
+
background-color: #1a73e8; // Primary blue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.mat-step-label {
|
|
22
|
+
font-size: 1.25rem; // Equivalent to Heading 2
|
|
23
|
+
font-weight: 400;
|
|
24
|
+
color: #3c4043;
|
|
25
|
+
font-family: 'Google Sans', sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.mat-step-label-selected {
|
|
29
|
+
font-weight: 500;
|
|
30
|
+
color: #202124;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.mat-vertical-content {
|
|
35
|
+
padding-left: 36px; // Align content with header text
|
|
36
|
+
padding-right: 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.step-content {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 16px;
|
|
44
|
+
max-width: 600px; // Standard form width
|
|
45
|
+
margin-top: 16px;
|
|
46
|
+
margin-bottom: 24px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.row {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: 8px;
|
|
53
|
+
|
|
54
|
+
&.direction-col {
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
mat-form-field {
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.form-section-header {
|
|
64
|
+
margin-bottom: 8px;
|
|
65
|
+
|
|
66
|
+
.section-title {
|
|
67
|
+
font-weight: 500;
|
|
68
|
+
font-size: 14px;
|
|
69
|
+
color: #3c4043;
|
|
70
|
+
display: block;
|
|
71
|
+
margin-bottom: 4px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.section-description {
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
color: #5f6368;
|
|
77
|
+
margin: 0;
|
|
78
|
+
line-height: 1.5;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.learn-more {
|
|
82
|
+
color: #1a73e8;
|
|
83
|
+
text-decoration: underline;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.vertical-radio-group {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
|
|
92
|
+
mat-radio-button {
|
|
93
|
+
font-size: 14px;
|
|
94
|
+
color: #3c4043;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.stepper-actions {
|
|
99
|
+
display: flex;
|
|
100
|
+
gap: 12px;
|
|
101
|
+
margin-top: 24px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.global-actions {
|
|
105
|
+
display: flex;
|
|
106
|
+
gap: 12px;
|
|
107
|
+
margin-top: 32px;
|
|
108
|
+
padding-top: 16px;
|
|
109
|
+
border-top: 1px solid #e8eaed; // Separator from stepper
|
|
110
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
4
|
+
import { MatStepperModule } from '@angular/material/stepper';
|
|
5
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
6
|
+
import { MatInputModule } from '@angular/material/input';
|
|
7
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
8
|
+
import { MatRadioModule } from '@angular/material/radio';
|
|
9
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
10
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
11
|
+
import { Router } from '@angular/router';
|
|
12
|
+
import { PageComponent } from '../../components/page/page';
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'app-complex-form',
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [
|
|
18
|
+
CommonModule,
|
|
19
|
+
ReactiveFormsModule,
|
|
20
|
+
MatStepperModule,
|
|
21
|
+
MatFormFieldModule,
|
|
22
|
+
MatInputModule,
|
|
23
|
+
MatSelectModule,
|
|
24
|
+
MatRadioModule,
|
|
25
|
+
MatButtonModule,
|
|
26
|
+
MatIconModule,
|
|
27
|
+
PageComponent
|
|
28
|
+
],
|
|
29
|
+
templateUrl: './complex-form.html',
|
|
30
|
+
styleUrl: './complex-form.scss'
|
|
31
|
+
})
|
|
32
|
+
export class ComplexFormComponent {
|
|
33
|
+
step1FormGroup: FormGroup;
|
|
34
|
+
step2FormGroup: FormGroup;
|
|
35
|
+
step3FormGroup: FormGroup;
|
|
36
|
+
step4FormGroup: FormGroup;
|
|
37
|
+
|
|
38
|
+
policyTypes = [
|
|
39
|
+
{ value: 'vpc', label: 'VPC policy' },
|
|
40
|
+
{ value: 'org', label: 'Organization policy' }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
constructor(private _formBuilder: FormBuilder, private router: Router) {
|
|
44
|
+
this.step1FormGroup = this._formBuilder.group({
|
|
45
|
+
policyName: ['', [Validators.required, Validators.pattern(/^[a-z0-9-]+$/)]],
|
|
46
|
+
description: [''],
|
|
47
|
+
policyType: ['vpc', Validators.required],
|
|
48
|
+
scope: ['global', Validators.required]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.step2FormGroup = this._formBuilder.group({});
|
|
52
|
+
this.step3FormGroup = this._formBuilder.group({});
|
|
53
|
+
this.step4FormGroup = this._formBuilder.group({});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onSubmit() {
|
|
57
|
+
if (this.step1FormGroup.valid) {
|
|
58
|
+
console.log('Form Submitted!', this.step1FormGroup.value);
|
|
59
|
+
this.router.navigate(['/templates/firewalls']);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cancel() {
|
|
64
|
+
this.router.navigate(['/templates/firewalls']);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<app-page title="Create Firewall Rule Template">
|
|
2
|
+
<button back-button mat-icon-button (click)="cancel()">
|
|
3
|
+
<mat-icon>arrow_back</mat-icon>
|
|
4
|
+
</button>
|
|
5
|
+
|
|
6
|
+
<div class="default-container-padding">
|
|
7
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="form-container">
|
|
8
|
+
<p>Fill out the form below to define a new firewall rule configuration.</p>
|
|
9
|
+
|
|
10
|
+
<div class="row">
|
|
11
|
+
<mat-form-field appearance="outline">
|
|
12
|
+
<mat-label>Name</mat-label>
|
|
13
|
+
<input matInput formControlName="name" placeholder="allow-traffic-80">
|
|
14
|
+
<mat-hint>Letters, numbers and dashes only, start with a letter.</mat-hint>
|
|
15
|
+
<mat-error *ngIf="form.get('name')?.hasError('required')">Name is required.</mat-error>
|
|
16
|
+
<mat-error *ngIf="form.get('name')?.hasError('pattern')">Invalid format (Lowercase letter followed by letters, numbers, hyphens).</mat-error>
|
|
17
|
+
</mat-form-field>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="row">
|
|
21
|
+
<mat-form-field appearance="outline">
|
|
22
|
+
<mat-label>Description</mat-label>
|
|
23
|
+
<textarea matInput formControlName="description" placeholder="Optional rule description" rows="3"></textarea>
|
|
24
|
+
</mat-form-field>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="row">
|
|
28
|
+
<mat-form-field appearance="outline">
|
|
29
|
+
<mat-label>Direction</mat-label>
|
|
30
|
+
<mat-select formControlName="direction">
|
|
31
|
+
<mat-option *ngFor="let dir of directions" [value]="dir.value">{{ dir.label }}</mat-option>
|
|
32
|
+
</mat-select>
|
|
33
|
+
</mat-form-field>
|
|
34
|
+
|
|
35
|
+
<mat-form-field appearance="outline">
|
|
36
|
+
<mat-label>Action</mat-label>
|
|
37
|
+
<mat-select formControlName="actionType">
|
|
38
|
+
<mat-option *ngFor="let act of actionTypes" [value]="act.value">{{ act.label }}</mat-option>
|
|
39
|
+
</mat-select>
|
|
40
|
+
</mat-form-field>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="row">
|
|
44
|
+
<mat-form-field appearance="outline">
|
|
45
|
+
<mat-label>Priority</mat-label>
|
|
46
|
+
<input matInput type="number" formControlName="priority" placeholder="1000">
|
|
47
|
+
<mat-hint>Higher number means lower priority (0 to 65535).</mat-hint>
|
|
48
|
+
</mat-form-field>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="row">
|
|
52
|
+
<mat-form-field appearance="outline">
|
|
53
|
+
<mat-label>Source IP Ranges (comma-separated)</mat-label>
|
|
54
|
+
<input matInput formControlName="ipRangesCsv" placeholder="10.0.0.0/8, 192.168.1.0/24">
|
|
55
|
+
<mat-error *ngIf="form.get('ipRangesCsv')?.hasError('required')">At least one IP range is required.</mat-error>
|
|
56
|
+
<mat-error *ngIf="form.get('ipRangesCsv')?.hasError('pattern')">Invalid IP range format (CIDR notation required, e.g., 0.0.0.0/0).</mat-error>
|
|
57
|
+
</mat-form-field>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="row">
|
|
61
|
+
<mat-form-field appearance="outline">
|
|
62
|
+
<mat-label>Protocols and Ports (comma-separated)</mat-label>
|
|
63
|
+
<input matInput formControlName="portsCsv" placeholder="tcp:80, udp:53, icmp">
|
|
64
|
+
<mat-error *ngIf="form.get('portsCsv')?.hasError('required')">At least one protocol/port rule is required.</mat-error>
|
|
65
|
+
</mat-form-field>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="checkbox-row" style="margin-top: 16px;">
|
|
69
|
+
<mat-checkbox formControlName="loggingEnabled">Enable logging for traffic matched by this rule</mat-checkbox>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="actions">
|
|
73
|
+
<button mat-flat-button color="primary" type="submit" [disabled]="form.invalid">Create</button>
|
|
74
|
+
<button mat-button type="button" (click)="cancel()">Cancel</button>
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
</div>
|
|
78
|
+
</app-page>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use '../../../_variables' as theme;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
4
|
+
import { MatInputModule } from '@angular/material/input';
|
|
5
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
6
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
7
|
+
import { MatCardModule } from '@angular/material/card';
|
|
8
|
+
import { PageComponent } from '../../components/page/page';
|
|
9
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
10
|
+
import { Router } from '@angular/router';
|
|
11
|
+
import { FirewallService } from '../firewall.service';
|
|
12
|
+
import { CommonModule } from '@angular/common';
|
|
13
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'app-golden-firewall-add',
|
|
17
|
+
standalone: true,
|
|
18
|
+
imports: [
|
|
19
|
+
CommonModule,
|
|
20
|
+
ReactiveFormsModule,
|
|
21
|
+
MatFormFieldModule,
|
|
22
|
+
MatInputModule,
|
|
23
|
+
MatSelectModule,
|
|
24
|
+
MatButtonModule,
|
|
25
|
+
MatCardModule,
|
|
26
|
+
PageComponent,
|
|
27
|
+
MatIconModule,
|
|
28
|
+
MatCheckboxModule
|
|
29
|
+
],
|
|
30
|
+
templateUrl: './firewall-add.html',
|
|
31
|
+
styleUrl: './firewall-add.scss'
|
|
32
|
+
})
|
|
33
|
+
export class FirewallTemplateAddComponent {
|
|
34
|
+
form: FormGroup;
|
|
35
|
+
|
|
36
|
+
directions = [
|
|
37
|
+
{ value: 'INGRESS', label: 'Ingress' },
|
|
38
|
+
{ value: 'EGRESS', label: 'Egress' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
actionTypes = [
|
|
42
|
+
{ value: 'ALLOW', label: 'Allow' },
|
|
43
|
+
{ value: 'DENY', label: 'Deny' }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private fb: FormBuilder,
|
|
48
|
+
private service: FirewallService,
|
|
49
|
+
private router: Router
|
|
50
|
+
) {
|
|
51
|
+
this.form = this.fb.group({
|
|
52
|
+
name: ['', [
|
|
53
|
+
Validators.required,
|
|
54
|
+
Validators.maxLength(63),
|
|
55
|
+
Validators.pattern('^[a-z]([-a-z0-9]*[a-z0-9])?$')
|
|
56
|
+
]],
|
|
57
|
+
description: [''],
|
|
58
|
+
direction: ['INGRESS', Validators.required],
|
|
59
|
+
priority: [1000, [Validators.required, Validators.min(0), Validators.max(65535)]],
|
|
60
|
+
actionType: ['ALLOW', Validators.required],
|
|
61
|
+
ipRangesCsv: ['', [
|
|
62
|
+
Validators.required,
|
|
63
|
+
Validators.pattern('^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))(,\\s*([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2])))*$')
|
|
64
|
+
]],
|
|
65
|
+
portsCsv: ['', Validators.required],
|
|
66
|
+
loggingEnabled: [false]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onSubmit() {
|
|
71
|
+
if (this.form.valid) {
|
|
72
|
+
const val = this.form.value;
|
|
73
|
+
const ipRanges = val.ipRangesCsv.split(',').map((t: string) => t.trim()).filter((t: string) => t.length > 0);
|
|
74
|
+
const ports = val.portsCsv.split(',').map((t: string) => t.trim()).filter((t: string) => t.length > 0);
|
|
75
|
+
|
|
76
|
+
this.service.addRule({
|
|
77
|
+
name: val.name,
|
|
78
|
+
description: val.description,
|
|
79
|
+
direction: val.direction,
|
|
80
|
+
priority: val.priority,
|
|
81
|
+
actionType: val.actionType,
|
|
82
|
+
ipRanges: ipRanges,
|
|
83
|
+
protocolsAndPortsForDisplay: ports,
|
|
84
|
+
loggingEnabled: val.loggingEnabled
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.router.navigate(['/templates/firewalls']);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
cancel() {
|
|
92
|
+
this.router.navigate(['/templates/firewalls']);
|
|
93
|
+
}
|
|
94
|
+
}
|