add teanant apps list
This commit is contained in:
parent
5a0ffcc73b
commit
7abcdcf8f1
@ -0,0 +1,5 @@
|
|||||||
|
export interface AppListDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
visibility: string;
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {lastValueFrom, Observable} from "rxjs";
|
import {lastValueFrom, Observable} from "rxjs";
|
||||||
import {PalantirService} from '../palantir.service';
|
import {PalantirService} from '../palantir.service';
|
||||||
import {SubjectListDto} from './dtos/subject-list-dto';
|
import {SubjectListDto} from '../dtos/subject-list-dto';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {lastValueFrom, Observable, of} from "rxjs";
|
import {lastValueFrom, Observable, of} from "rxjs";
|
||||||
import {PalantirService} from '../palantir.service';
|
import {PalantirService} from '../palantir.service';
|
||||||
import {TenantGridViewDto} from './dtos/tenant-grid-view-dto';
|
import {AppListDto} from '../dtos/app-list-dto';
|
||||||
|
import {TenantGridViewDto} from '../dtos/tenant-grid-view-dto';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -30,4 +31,12 @@ export class TenantService extends PalantirService {
|
|||||||
getTenantAsync(id: string | null, onError?: (error: any) => void): Promise<TenantGridViewDto | null> {
|
getTenantAsync(id: string | null, onError?: (error: any) => void): Promise<TenantGridViewDto | null> {
|
||||||
return lastValueFrom(this.getTenant$(id, onError));
|
return lastValueFrom(this.getTenant$(id, onError));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTenantApps$(id: string, onError?: (error: any) => void): Observable<AppListDto[]> {
|
||||||
|
return this.handleRequest(this.http.get<AppListDto[]>(this.baseUrl + `/${id}/apps`), onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTenantAppsAsync(id: string, onError?: (error: any) => void): Promise<AppListDto[]> {
|
||||||
|
return lastValueFrom(this.getTenantApps$(id, onError));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
@if (loading()) {
|
||||||
|
Loading...
|
||||||
|
} @else {
|
||||||
|
@for (app of apps(); track app.id) {
|
||||||
|
<app-panel class="neutral-80">
|
||||||
|
<span class="title">{{app.name}}</span>
|
||||||
|
<span class="actions">
|
||||||
|
<button [routerLink]="'app/' + app.id" class="primary outline">View Details</button>
|
||||||
|
</span>
|
||||||
|
</app-panel>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .5rem;
|
||||||
|
|
||||||
|
app-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AppListComponent } from './app-list.component';
|
||||||
|
|
||||||
|
describe('AppListComponent', () => {
|
||||||
|
let component: AppListComponent;
|
||||||
|
let fixture: ComponentFixture<AppListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AppListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AppListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import {Component, inject, input, OnInit, signal} from '@angular/core';
|
||||||
|
import {AppListDto} from '../../../clients/gandalf/mithrandir/dtos/app-list-dto';
|
||||||
|
import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service';
|
||||||
|
import {PanelComponent} from '../../panel/panel.component';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-app-list',
|
||||||
|
imports: [
|
||||||
|
PanelComponent,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './app-list.component.html',
|
||||||
|
styleUrl: './app-list.component.scss',
|
||||||
|
})
|
||||||
|
export class AppListComponent implements OnInit {
|
||||||
|
|
||||||
|
public tenantId = input<string>()
|
||||||
|
|
||||||
|
protected loading = signal<boolean>(true);
|
||||||
|
protected apps = signal<AppListDto[]>([]);
|
||||||
|
|
||||||
|
private tenantService = inject(TenantService);
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
|
||||||
|
const tenantId = this.tenantId();
|
||||||
|
|
||||||
|
if (tenantId) {
|
||||||
|
const apps = await this.tenantService.getTenantAppsAsync(tenantId);
|
||||||
|
this.apps.set(apps);
|
||||||
|
this.loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ import {PanelComponent} from '../../panel/panel.component';
|
|||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
import {NgClass} from '@angular/common';
|
import {NgClass} from '@angular/common';
|
||||||
import {SubjectService} from '../../../clients/gandalf/mithrandir/subject/subject.service';
|
import {SubjectService} from '../../../clients/gandalf/mithrandir/subject/subject.service';
|
||||||
import {SubjectListDto} from '../../../clients/gandalf/mithrandir/subject/dtos/subject-list-dto';
|
import {SubjectListDto} from '../../../clients/gandalf/mithrandir/dtos/subject-list-dto';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subject-list',
|
selector: 'app-subject-list',
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template tabId="apps" let-tab>
|
<ng-template tabId="apps" let-tab>
|
||||||
{{tab.name}} - content
|
<app-app-list [tenantId]="tenantId()!"></app-app-list>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template tabId="groups" let-tab>
|
<ng-template tabId="groups" let-tab>
|
||||||
|
|||||||
@ -3,13 +3,15 @@ import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
|
|||||||
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
||||||
import {ActivatedRoute, Router} from '@angular/router';
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
import {SubjectListComponent} from '../../subject/subject-list/subject-list.component';
|
import {SubjectListComponent} from '../../subject/subject-list/subject-list.component';
|
||||||
|
import {AppListComponent} from '../../app/app-list/app-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tenant-detail',
|
selector: 'app-tenant-detail',
|
||||||
imports: [
|
imports: [
|
||||||
TabGroupComponent,
|
TabGroupComponent,
|
||||||
ActiveTabDirective,
|
ActiveTabDirective,
|
||||||
SubjectListComponent
|
SubjectListComponent,
|
||||||
|
AppListComponent
|
||||||
],
|
],
|
||||||
templateUrl: './tenant-detail.component.html',
|
templateUrl: './tenant-detail.component.html',
|
||||||
styleUrl: './tenant-detail.component.scss',
|
styleUrl: './tenant-detail.component.scss',
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import {Component, inject, OnInit, signal} from '@angular/core';
|
import {Component, inject, OnInit, signal} from '@angular/core';
|
||||||
import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service';
|
import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service';
|
||||||
import {TenantGridViewDto} from '../../../clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto';
|
|
||||||
import {PanelComponent} from '../../panel/panel.component';
|
import {PanelComponent} from '../../panel/panel.component';
|
||||||
import {LinkComponent} from '../../link/link.component';
|
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
import {NgClass} from '@angular/common';
|
import {NgClass} from '@angular/common';
|
||||||
|
import {TenantGridViewDto} from '../../../clients/gandalf/mithrandir/dtos/tenant-grid-view-dto';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tenant-grid',
|
selector: 'app-tenant-grid',
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import {ResolveFn} from "@angular/router";
|
import {ResolveFn} from "@angular/router";
|
||||||
import {TenantGridViewDto} from "../clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto";
|
|
||||||
import {inject} from "@angular/core";
|
import {inject} from "@angular/core";
|
||||||
import {TenantService} from "../clients/gandalf/mithrandir/tenant/tenant.service";
|
import {TenantService} from "../clients/gandalf/mithrandir/tenant/tenant.service";
|
||||||
import {map} from 'rxjs';
|
import {map} from 'rxjs';
|
||||||
|
import {TenantGridViewDto} from '../clients/gandalf/mithrandir/dtos/tenant-grid-view-dto';
|
||||||
|
|
||||||
export const tenantResolver: ResolveFn<TenantGridViewDto | null> = (route, state) => {
|
export const tenantResolver: ResolveFn<TenantGridViewDto | null> = (route, state) => {
|
||||||
const tenantService = inject(TenantService);
|
const tenantService = inject(TenantService);
|
||||||
|
|||||||
@ -4,7 +4,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Suspectus.Gandalf.Palantir.Abstractions;
|
using Suspectus.Gandalf.Palantir.Abstractions;
|
||||||
using Suspectus.Gandalf.Palantir.Data.Database;
|
using Suspectus.Gandalf.Palantir.Data.Database;
|
||||||
|
using Suspectus.Gandalf.Palantir.Data.Dto.App;
|
||||||
using Suspectus.Gandalf.Palantir.Data.Dto.Tenant;
|
using Suspectus.Gandalf.Palantir.Data.Dto.Tenant;
|
||||||
|
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||||
|
|
||||||
namespace Suspectus.Gandalf.Palantir.Api.Controllers;
|
namespace Suspectus.Gandalf.Palantir.Api.Controllers;
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ public class TenantController : ControllerBase
|
|||||||
_context = context;
|
_context = context;
|
||||||
_hashids = hashids;
|
_hashids = hashids;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Get(InvokerContext invokerContext, CancellationToken cancellationToken)
|
public async Task<IActionResult> Get(InvokerContext invokerContext, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -29,7 +31,7 @@ public class TenantController : ControllerBase
|
|||||||
.Where(x => x.Id!.Value == invokerContext.Invoker!.SubjectId)
|
.Where(x => x.Id!.Value == invokerContext.Invoker!.SubjectId)
|
||||||
.SelectMany(x => x.Tenants)
|
.SelectMany(x => x.Tenants)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
var dtos = tenantEntities.Select(x => new TenantGridViewDto
|
var dtos = tenantEntities.Select(x => new TenantGridViewDto
|
||||||
{
|
{
|
||||||
Id = _hashids.EncodeLong(x.Id!.Value),
|
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||||
@ -41,23 +43,26 @@ public class TenantController : ControllerBase
|
|||||||
});
|
});
|
||||||
return Ok(dtos);
|
return Ok(dtos);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{idHash}")]
|
[HttpGet("{idHash}")]
|
||||||
public async Task<IActionResult> Get(CancellationToken cancellationToken, string idHash, InvokerContext invokerContext)
|
public async Task<IActionResult> Get(CancellationToken cancellationToken, string idHash,
|
||||||
|
InvokerContext invokerContext)
|
||||||
{
|
{
|
||||||
if (!_hashids.TryDecodeSingleLong(idHash, out var id))
|
if (!_hashids.TryDecodeSingleLong(idHash, out var id))
|
||||||
{
|
{
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenant = await _context.Tenants.SingleOrDefaultAsync(x => x.Id == id, cancellationToken);
|
var tenant = await _context.Tenants.SingleOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
|
|
||||||
if (tenant is null)
|
if (tenant is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var userHasRelation = await _context.TenantSubjectRelations.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == id, cancellationToken: cancellationToken);
|
var userHasRelation = await _context.TenantSubjectRelations.AnyAsync(
|
||||||
|
x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == id,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (!userHasRelation)
|
if (!userHasRelation)
|
||||||
{
|
{
|
||||||
@ -73,7 +78,47 @@ public class TenantController : ControllerBase
|
|||||||
OwnerId = _hashids.EncodeLong(tenant.OwnerId),
|
OwnerId = _hashids.EncodeLong(tenant.OwnerId),
|
||||||
Visibility = tenant.Visibility
|
Visibility = tenant.Visibility
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(dto);
|
return Ok(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{tenantIdHash}/apps")]
|
||||||
|
public async Task<IActionResult> GetTenantApps(InvokerContext invokerContext, string tenantIdHash,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_hashids.TryDecodeSingleLong(tenantIdHash, out var tenantId))
|
||||||
|
{
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenantExists = await _context.Tenants.AnyAsync(x => x.Id == tenantId, cancellationToken);
|
||||||
|
|
||||||
|
if (!tenantExists)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationExists = await _context.TenantSubjectRelations
|
||||||
|
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId, cancellationToken);
|
||||||
|
|
||||||
|
if (!relationExists)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var apps = await _context.AppSubjectRelations
|
||||||
|
.Where(x => x.SubjectId == invokerContext.Invoker!.SubjectId)
|
||||||
|
.Where(x => x.App!.TenantId == tenantId)
|
||||||
|
.Select(x => x.App!)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var dtos = apps.Select(x => new AppListDto
|
||||||
|
{
|
||||||
|
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||||
|
Name = x.Name,
|
||||||
|
Visibility = x.Visibility
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(dtos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||||
|
|
||||||
|
namespace Suspectus.Gandalf.Palantir.Data.Dto.App;
|
||||||
|
|
||||||
|
public class AppListDto
|
||||||
|
{
|
||||||
|
public required string Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required EntityVisibility Visibility { get; set; }
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user