Skip to content

Commit a865a47

Browse files
Merge pull request #1584 from karinakharchenko/audit_v3
Audit v3
2 parents 841ca65 + 152a030 commit a865a47

File tree

50 files changed

+2457
-880
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2457
-880
lines changed

backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { BaseType } from '../../../common/data-injection.tokens.js';
55
import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js';
66
import { IExportCSVFromTable } from './table-use-cases.interface.js';
77
import { Messages } from '../../../exceptions/text/messages.js';
8+
import { LogOperationTypeEnum, OperationResultStatusEnum } from '../../../enums/index.js';
9+
import { TableLogsService } from '../../table-logs/table-logs.service.js';
810
import { findFilteringFieldsUtil, parseFilteringFieldsFromBodyData } from '../utils/find-filtering-fields.util.js';
911
import { findOrderingFieldUtil } from '../utils/find-ordering-field.util.js';
1012
import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js';
@@ -26,6 +28,7 @@ export class ExportCSVFromTableUseCase
2628
constructor(
2729
@Inject(BaseType.GLOBAL_DB_CONTEXT)
2830
protected _dbContext: IGlobalDatabaseContext,
31+
private tableLogsService: TableLogsService,
2932
) {
3033
super();
3134
}
@@ -47,6 +50,8 @@ export class ExportCSVFromTableUseCase
4750
throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN);
4851
}
4952

53+
let operationResult = OperationResultStatusEnum.unknown;
54+
5055
try {
5156
const dao = getDataAccessObject(connection);
5257

@@ -103,6 +108,8 @@ export class ExportCSVFromTableUseCase
103108
filteringFields,
104109
);
105110

111+
operationResult = OperationResultStatusEnum.successfully;
112+
106113
//todo: rework as streams when node oracle driver will support it correctly
107114
//todo: agent return data as array of table rows, not as stream, because we cant
108115
//todo: transfer data as a stream from clint to server
@@ -119,6 +126,7 @@ export class ExportCSVFromTableUseCase
119126
}
120127
return new StreamableFile(rowsStream.pipe(csv.stringify({ header: true })));
121128
} catch (error) {
129+
operationResult = OperationResultStatusEnum.unsuccessfully;
122130
if (error instanceof HttpException) {
123131
throw error;
124132
}
@@ -136,6 +144,15 @@ export class ExportCSVFromTableUseCase
136144
},
137145
HttpStatus.INTERNAL_SERVER_ERROR,
138146
);
147+
} finally {
148+
const logRecord = {
149+
table_name: tableName,
150+
userId: userId,
151+
connection: connection,
152+
operationType: LogOperationTypeEnum.exportRows,
153+
operationStatusResult: operationResult,
154+
};
155+
await this.tableLogsService.crateAndSaveNewLogUtil(logRecord);
139156
}
140157
}
141158
}

frontend/src/app/app-routing.module.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ const routes: Routes = [
8686
loadComponent: () => import('./components/company/company.component').then((m) => m.CompanyComponent),
8787
canActivate: [AuthGuard],
8888
},
89+
{
90+
path: 'branding',
91+
pathMatch: 'full',
92+
loadComponent: () => import('./components/branding/branding.component').then((m) => m.BrandingComponent),
93+
canActivate: [AuthGuard],
94+
title: 'Branding | Rocketadmin',
95+
},
96+
{
97+
path: 'api-keys',
98+
pathMatch: 'full',
99+
loadComponent: () => import('./components/api-keys/api-keys.component').then((m) => m.ApiKeysComponent),
100+
canActivate: [AuthGuard],
101+
title: 'API Keys | Rocketadmin',
102+
},
89103
{
90104
path: 'secrets',
91105
pathMatch: 'full',
@@ -94,10 +108,11 @@ const routes: Routes = [
94108
title: 'Secrets | Rocketadmin',
95109
},
96110
{
97-
path: 'sso/:company-id',
111+
path: 'saml',
98112
pathMatch: 'full',
99113
loadComponent: () => import('./components/sso/sso.component').then((m) => m.SsoComponent),
100114
canActivate: [AuthGuard],
115+
title: 'SAML SSO | Rocketadmin',
101116
},
102117
{
103118
path: 'change-password',
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
.profile-layout {
2+
display: flex;
3+
height: calc(100vh - 64px);
4+
}
5+
6+
.profile-main {
7+
flex: 1;
8+
overflow-y: auto;
9+
}
10+
11+
::ng-deep .profile-main > app-alert {
12+
position: relative;
13+
top: 0;
14+
margin: 24px;
15+
}
16+
17+
.api-keys-page {
18+
margin: var(--top-margin) auto;
19+
padding: 0 clamp(40px, 5vw, 100px);
20+
max-width: 800px;
21+
}
22+
23+
@media (width <= 600px) {
24+
.api-keys-page {
25+
padding: 0 16px;
26+
margin: 1.5em auto;
27+
}
28+
}
29+
30+
.api-keys-description {
31+
color: rgba(0, 0, 0, 0.64);
32+
margin-top: 8px;
33+
margin-bottom: 32px;
34+
}
35+
36+
@media (prefers-color-scheme: dark) {
37+
.api-keys-description {
38+
color: rgba(255, 255, 255, 0.7);
39+
}
40+
}
41+
42+
.api-keys-content {
43+
display: flex;
44+
flex-direction: column;
45+
gap: 24px;
46+
}
47+
48+
.api-key-form {
49+
display: flex;
50+
align-items: flex-start;
51+
gap: 16px;
52+
}
53+
54+
@media (width <= 600px) {
55+
.api-key-form {
56+
flex-direction: column;
57+
align-items: stretch;
58+
}
59+
}
60+
61+
.api-key-form__input {
62+
flex: 1;
63+
}
64+
65+
@media (width <= 600px) {
66+
.api-key-form__input {
67+
width: 100%;
68+
}
69+
}
70+
71+
.api-key-form__generate-button {
72+
margin-top: 4px;
73+
}
74+
75+
@media (width <= 600px) {
76+
.api-key-form__generate-button {
77+
margin-top: 0;
78+
}
79+
}
80+
81+
.api-key-value {
82+
display: flex;
83+
align-items: flex-start;
84+
gap: 8px;
85+
padding: 16px;
86+
background-color: rgba(76, 175, 80, 0.08);
87+
border-radius: 8px;
88+
border: 1px solid rgba(76, 175, 80, 0.24);
89+
}
90+
91+
@media (prefers-color-scheme: dark) {
92+
.api-key-value {
93+
background-color: rgba(76, 175, 80, 0.12);
94+
border-color: rgba(76, 175, 80, 0.32);
95+
}
96+
}
97+
98+
.api-key-value__input {
99+
flex: 1;
100+
}
101+
102+
.api-key-value__copy-button {
103+
margin-top: 4px;
104+
}
105+
106+
.api-keys-list {
107+
margin-top: 16px;
108+
}
109+
110+
.api-keys-list__heading {
111+
margin-bottom: 16px;
112+
font-weight: 500;
113+
}
114+
115+
.api-keys-empty {
116+
display: flex;
117+
flex-direction: column;
118+
align-items: center;
119+
padding: 48px 24px;
120+
text-align: center;
121+
background-color: rgba(0, 0, 0, 0.02);
122+
border-radius: 8px;
123+
}
124+
125+
@media (prefers-color-scheme: dark) {
126+
.api-keys-empty {
127+
background-color: rgba(255, 255, 255, 0.05);
128+
}
129+
}
130+
131+
.api-keys-empty__icon {
132+
font-size: 48px;
133+
width: 48px;
134+
height: 48px;
135+
color: rgba(0, 0, 0, 0.26);
136+
margin-bottom: 16px;
137+
}
138+
139+
@media (prefers-color-scheme: dark) {
140+
.api-keys-empty__icon {
141+
color: rgba(255, 255, 255, 0.3);
142+
}
143+
}
144+
145+
.api-keys-empty p {
146+
margin: 0;
147+
color: rgba(0, 0, 0, 0.54);
148+
}
149+
150+
@media (prefers-color-scheme: dark) {
151+
.api-keys-empty p {
152+
color: rgba(255, 255, 255, 0.54);
153+
}
154+
}
155+
156+
.api-keys-items {
157+
padding: 0;
158+
}
159+
160+
.api-key-list-item {
161+
border-radius: 8px;
162+
margin-bottom: 8px;
163+
}
164+
165+
.api-key-list-item:hover {
166+
background-color: rgba(0, 0, 0, 0.04);
167+
}
168+
169+
@media (prefers-color-scheme: dark) {
170+
.api-key-list-item:hover {
171+
background-color: rgba(255, 255, 255, 0.08);
172+
}
173+
}
174+
175+
.api-key-item {
176+
display: flex;
177+
align-items: center;
178+
justify-content: space-between;
179+
width: 100%;
180+
}
181+
182+
.api-key-item__info {
183+
display: flex;
184+
align-items: center;
185+
gap: 12px;
186+
}
187+
188+
.api-key-item__icon {
189+
color: rgba(0, 0, 0, 0.54);
190+
}
191+
192+
@media (prefers-color-scheme: dark) {
193+
.api-key-item__icon {
194+
color: rgba(255, 255, 255, 0.54);
195+
}
196+
}
197+
198+
.api-key-item__title {
199+
font-weight: 500;
200+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<div class="profile-layout">
2+
<app-profile-sidebar activeTab="api"></app-profile-sidebar>
3+
4+
<div class="profile-main">
5+
<app-alert></app-alert>
6+
7+
<div class="api-keys-page">
8+
<h1 class="mat-h1">API Keys</h1>
9+
<p class="api-keys-description">
10+
Generate and manage API keys to access Rocketadmin programmatically.
11+
</p>
12+
13+
<div class="api-keys-content">
14+
<form class="api-key-form" (ngSubmit)="generateAPIkey()">
15+
<mat-form-field appearance="outline" class="api-key-form__input">
16+
<mat-label>API key title</mat-label>
17+
<input matInput name="api-key-title" #apiKeyTitle="ngModel"
18+
data-testid="api-key-input"
19+
angulartics2On="change"
20+
angularticsAction="API Keys: api key title is edited"
21+
[(ngModel)]="generatingAPIkeyTitle">
22+
</mat-form-field>
23+
<button type="submit" mat-flat-button color="primary" data-testid="generate-api-key-button"
24+
class="api-key-form__generate-button"
25+
[disabled]="!generatingAPIkeyTitle || submitting">
26+
{{submitting ? 'Generating...' : 'Generate'}}
27+
</button>
28+
</form>
29+
30+
<div *ngIf="generatedAPIkeyHash" class="api-key-value">
31+
<mat-form-field appearance="outline" class="api-key-value__input">
32+
<mat-label>API key</mat-label>
33+
<input matInput name="token" #token="ngModel" readonly
34+
data-testid="api-key-value-input"
35+
[(ngModel)]="generatedAPIkeyHash">
36+
<mat-hint>
37+
Please save this API key. You won't be able to see it again.
38+
</mat-hint>
39+
</mat-form-field>
40+
<button type="button" mat-icon-button
41+
data-testid="api-key-value-copy-button"
42+
class="api-key-value__copy-button"
43+
matTooltip="Copy API key"
44+
[cdkCopyToClipboard]="generatedAPIkeyHash"
45+
(cdkCopyToClipboardCopied)="showCopyNotification('API key was copied to clipboard.')">
46+
<mat-icon>content_copy</mat-icon>
47+
</button>
48+
</div>
49+
50+
<div class="api-keys-list">
51+
<h3 class="api-keys-list__heading">Your API keys</h3>
52+
53+
<app-placeholder-api-keys-list *ngIf="!apiKeys"></app-placeholder-api-keys-list>
54+
55+
<div *ngIf="apiKeys && apiKeys.length === 0" class="api-keys-empty">
56+
<mat-icon class="api-keys-empty__icon">vpn_key_off</mat-icon>
57+
<p>You don't have any API keys yet.</p>
58+
</div>
59+
60+
<mat-list role="list" *ngIf="apiKeys && apiKeys.length" class="api-keys-items">
61+
<mat-list-item role="listitem" *ngFor="let apiKey of apiKeys; let i=index" class="api-key-list-item">
62+
<div class="api-key-item">
63+
<div class="api-key-item__info">
64+
<mat-icon class="api-key-item__icon">vpn_key</mat-icon>
65+
<span class="api-key-item__title">{{apiKey.title}}</span>
66+
</div>
67+
<button type="button" mat-icon-button
68+
attr.data-testid="api-key-{{i}}-delete-button"
69+
matTooltip="Delete API key"
70+
color="warn"
71+
(click)="deleteAPIkey(apiKey)">
72+
<mat-icon>delete</mat-icon>
73+
</button>
74+
</div>
75+
</mat-list-item>
76+
</mat-list>
77+
</div>
78+
</div>
79+
</div>
80+
</div>
81+
</div>

0 commit comments

Comments
 (0)