From 72b0d80411e2492321a796b8f7fbed114f0b4f35 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:17:33 +0200 Subject: [PATCH] Task/move details of granted access from table to dialog on access page (#7792) * Move details of granted access * Update changelog --- CHANGELOG.md | 1 + .../access-table/access-table.component.html | 38 ------------------- .../access-table/access-table.component.ts | 26 +------------ ...reate-or-update-access-dialog.component.ts | 37 +++++++++++++++++- .../create-or-update-access-dialog.html | 12 ++++++ 5 files changed, 49 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d33dca3..a0db33f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the details of the granted access from the table to the dialog on the access page (experimental) - Restricted the _Restricted view and manage_ permission of the access to share the portfolio to the Model Context Protocol (MCP) (experimental) - Migrated the transfer cash balance dialog to a dedicated route - Improved the language localization for Italian (`it`) diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html index fc324fb87..b8c2f8422 100644 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ b/apps/client/src/app/components/access-table/access-table.component.html @@ -45,36 +45,6 @@ - - Details - - @if (element.type === 'PUBLIC') { -
- - {{ - getPublicUrl(element.id) - }} -
- @if (user()?.settings?.isExperimentalFeatures) { -
- GET {{ baseUrl }}/api/v1/public/{{ - element.id - }}/portfolio -
- } - } @else if (element.type === 'MCP' && hasPermissionToEnableMcp) { -
- {{ baseUrl }}{{ mcpEndpoint }} -
-
- Authorization: Bearer {{ element.id }} -
- } - -
- @@ -113,14 +83,6 @@ } - @if (element.type === 'MCP' && hasPermissionToEnableMcp) { - - } @if ( (!isReceivedAccess() && user()?.settings?.isExperimentalFeatures) || element.type === 'PUBLIC' || diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index baf4247e3..9b666b3b6 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/apps/client/src/app/components/access-table/access-table.component.ts @@ -1,4 +1,3 @@ -import { MCP_ENDPOINT } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { Access, User } from '@ghostfolio/common/interfaces'; @@ -32,7 +31,6 @@ import { copyOutline, createOutline, ellipsisHorizontal, - linkOutline, removeCircleOutline, trashOutline } from 'ionicons/icons'; @@ -81,14 +79,7 @@ export class GfAccessTableComponent { protected readonly dataSource = new MatTableDataSource(); protected readonly displayedColumns = computed(() => { - const columns = [ - 'alias', - 'grantee', - 'type', - 'lastUsedAt', - 'expiresAt', - 'details' - ]; + const columns = ['alias', 'grantee', 'type', 'lastUsedAt', 'expiresAt']; if (this.showActions()) { columns.push('actions'); @@ -109,8 +100,6 @@ export class GfAccessTableComponent { return !this.accesses(); }); - protected readonly mcpEndpoint = MCP_ENDPOINT; - private readonly clipboard = inject(Clipboard); private readonly dataService = inject(DataService); private readonly notificationService = inject(NotificationService); @@ -121,7 +110,6 @@ export class GfAccessTableComponent { copyOutline, createOutline, ellipsisHorizontal, - linkOutline, removeCircleOutline, trashOutline }); @@ -142,18 +130,6 @@ export class GfAccessTableComponent { return `${this.baseUrl}/${languageCode}/${publicRoutes.public.path}/${aId}`; } - protected onCopyIdToClipboard(aId: string) { - this.clipboard.copy(aId); - - this.snackBar.open( - '✅ ' + $localize`Identifier has been copied to the clipboard`, - undefined, - { - duration: ms('3 seconds') - } - ); - } - protected onCopyUrlToClipboard(aId: string) { this.clipboard.copy(this.getPublicUrl(aId)); diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 8c2bb0353..98275e5e7 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,5 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; +import { DEFAULT_LOCALE, MCP_ENDPOINT } from '@ghostfolio/common/config'; import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { canApplyFiltersToAccess } from '@ghostfolio/common/helper'; import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; @@ -27,6 +27,7 @@ import { } from '@ghostfolio/ui/portfolio-filter-form'; import { DataService } from '@ghostfolio/ui/services'; +import { JsonPipe } from '@angular/common'; import type { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectionStrategy, @@ -69,6 +70,7 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; FormsModule, GfAccessLevelIconComponent, GfPortfolioFilterFormComponent, + JsonPipe, MatButtonModule, MatDatepickerModule, MatDialogModule, @@ -88,6 +90,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { public tags: Filter[] = []; protected accessForm: FormGroup; + protected readonly baseUrl = window.location.origin; protected minExpiresAt: Date; protected readonly mode: 'create' | 'update'; protected readonly today = startOfDay(new Date()); @@ -222,6 +225,10 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { this.loadHoldings(); } + protected get accessId() { + return this.data.access?.id; + } + protected get accessLevel(): AccessLevel { return this.accessForm?.get('accessLevel')?.value as AccessLevel; } @@ -234,10 +241,32 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { return this.accessType === 'PUBLIC'; } + protected get mcpConfiguration() { + return { + headers: { + Authorization: `Bearer ${this.accessId}` + }, + type: 'http', + url: `${this.baseUrl}${MCP_ENDPOINT}` + }; + } + protected get showExpiresAtErrorMessage() { return this.accessForm?.get('expiresAt')?.invalid === true; } + protected get showMcpDetails() { + return this.canGrantMcpAccess && this.isMcpAccess && this.mode === 'update'; + } + + protected get showPublicDetails() { + return ( + this.hasExperimentalFeatures && + this.isPublicAccess && + this.mode === 'update' + ); + } + protected onCancel() { this.dialogRef.close(); } @@ -250,6 +279,10 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { } } + private get isMcpAccess() { + return this.accessType === 'MCP'; + } + private async createAccess() { const filters = this.getFilters(); @@ -343,7 +376,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { } private async updateAccess() { - const accessId = this.data.access?.id; + const accessId = this.accessId; if (!accessId) { return; diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html index 21c0cd3a8..31391beb0 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -133,6 +133,18 @@ [tags]="tags" /> } + @if (showMcpDetails || showPublicDetails) { +

Details

+ @if (showMcpDetails) { +
{{ mcpConfiguration | json }}
+ } @else if (showPublicDetails) { +
+ GET {{ baseUrl }}/api/v1/public/{{ accessId }}/portfolio +
+ } + }