mirror of
https://github.com/ceph/ceph
synced 2026-08-02 07:03:18 +00:00
Merge pull request #61183 from rhcs-dashboard/smb-join_auth-usersgroups-mgmt
mgr/dashboard: smb join-auth and usersgroups resources listing Reviewed-by: Afreen Misbah <afreen@ibm.com>
This commit is contained in:
commit
d361ca0cd8
@ -88,6 +88,41 @@ SHARE_SCHEMA = {
|
||||
}, "Configuration for the CephFS share")
|
||||
}
|
||||
|
||||
JOIN_AUTH_SCHEMA = {
|
||||
"resource_type": (str, "ceph.smb.join.auth"),
|
||||
"auth_id": (str, "Unique identifier for the join auth resource"),
|
||||
"intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
|
||||
"auth": ({
|
||||
"username": (str, "Username for authentication"),
|
||||
"password": (str, "Password for authentication")
|
||||
}, "Authentication credentials"),
|
||||
"linked_to_cluster": (str, "Optional string containing a cluster ID. \
|
||||
If set, the resource is linked to the cluster and will be automatically removed \
|
||||
when the cluster is removed")
|
||||
}
|
||||
|
||||
LIST_JOIN_AUTH_SCHEMA = [JOIN_AUTH_SCHEMA]
|
||||
|
||||
USERSGROUPS_SCHEMA = {
|
||||
"resource_type": (str, "ceph.smb.usersgroups"),
|
||||
"users_groups_id": (str, "A short string identifying the usersgroups resource"),
|
||||
"intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
|
||||
"values": ({
|
||||
"users": ([{
|
||||
"name": (str, "The user name"),
|
||||
"password": (str, "The password for the user")
|
||||
}], "List of user objects, each containing a name and password"),
|
||||
"groups": ([{
|
||||
"name": (str, "The name of the group")
|
||||
}], "List of group objects, each containing a name")
|
||||
}, "Required object containing users and groups information"),
|
||||
"linked_to_cluster": (str, "Optional string containing a cluster ID. \
|
||||
If set, the resource is linked to the cluster and will be automatically removed \
|
||||
when the cluster is removed")
|
||||
}
|
||||
|
||||
LIST_USERSGROUPS_SCHEMA = [USERSGROUPS_SCHEMA]
|
||||
|
||||
|
||||
def raise_on_failure(func):
|
||||
@wraps(func)
|
||||
@ -227,6 +262,50 @@ class SMBShare(RESTController):
|
||||
return mgr.remote('smb', 'apply_resources', json.dumps(resource)).one().to_simplified()
|
||||
|
||||
|
||||
@APIRouter('/smb/joinauth', Scope.SMB)
|
||||
@APIDoc("SMB Join Auth API", "SMB")
|
||||
class SMBJoinAuth(RESTController):
|
||||
_resource: str = 'ceph.smb.join.auth'
|
||||
|
||||
@ReadPermission
|
||||
@EndpointDoc("List smb join authorization resources",
|
||||
responses={200: LIST_JOIN_AUTH_SCHEMA})
|
||||
def list(self, join_auth: str = '') -> List[Share]:
|
||||
"""
|
||||
List all smb join auth resources
|
||||
|
||||
:return: Returns list of join auth.
|
||||
:rtype: List[Dict]
|
||||
"""
|
||||
res = mgr.remote(
|
||||
'smb',
|
||||
'show',
|
||||
[f'{self._resource}.{join_auth}' if join_auth else self._resource])
|
||||
return res['resources'] if 'resources' in res else [res]
|
||||
|
||||
|
||||
@APIRouter('/smb/usersgroups', Scope.SMB)
|
||||
@APIDoc("SMB Users Groups API", "SMB")
|
||||
class SMBUsersgroups(RESTController):
|
||||
_resource: str = 'ceph.smb.usersgroups'
|
||||
|
||||
@ReadPermission
|
||||
@EndpointDoc("List smb user resources",
|
||||
responses={200: LIST_USERSGROUPS_SCHEMA})
|
||||
def list(self, users_groups: str = '') -> List[Share]:
|
||||
"""
|
||||
List all smb usersgroups resources
|
||||
|
||||
:return: Returns list of usersgroups.
|
||||
:rtype: List[Dict]
|
||||
"""
|
||||
res = mgr.remote(
|
||||
'smb',
|
||||
'show',
|
||||
[f'{self._resource}.{users_groups}' if users_groups else self._resource])
|
||||
return res['resources'] if 'resources' in res else [res]
|
||||
|
||||
|
||||
@UIRouter('/smb')
|
||||
class SMBStatus(RESTController):
|
||||
@EndpointDoc("Get SMB feature status")
|
||||
|
||||
@ -51,8 +51,8 @@ import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progres
|
||||
import { MultiClusterComponent } from './ceph/cluster/multi-cluster/multi-cluster.component';
|
||||
import { MultiClusterListComponent } from './ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component';
|
||||
import { MultiClusterDetailsComponent } from './ceph/cluster/multi-cluster/multi-cluster-details/multi-cluster-details.component';
|
||||
import { SmbClusterListComponent } from './ceph/smb/smb-cluster-list/smb-cluster-list.component';
|
||||
import { SmbClusterFormComponent } from './ceph/smb/smb-cluster-form/smb-cluster-form.component';
|
||||
import { SmbTabsComponent } from './ceph/smb/smb-tabs/smb-tabs.component';
|
||||
|
||||
@Injectable()
|
||||
export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
|
||||
@ -446,7 +446,7 @@ const routes: Routes = [
|
||||
breadcrumbs: 'File/SMB'
|
||||
},
|
||||
children: [
|
||||
{ path: '', component: SmbClusterListComponent },
|
||||
{ path: '', component: SmbTabsComponent },
|
||||
{
|
||||
path: `${URLVerbs.CREATE}`,
|
||||
component: SmbClusterFormComponent,
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
CLUSTERING,
|
||||
PLACEMENT,
|
||||
RequestModel,
|
||||
RESOURCE_TYPE,
|
||||
CLUSTER_RESOURCE,
|
||||
RESOURCE,
|
||||
DomainSettings,
|
||||
JoinSource
|
||||
@ -214,7 +214,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
|
||||
|
||||
const requestModel: RequestModel = {
|
||||
cluster_resource: {
|
||||
resource_type: RESOURCE_TYPE,
|
||||
resource_type: CLUSTER_RESOURCE,
|
||||
cluster_id: rawFormValue.cluster_id,
|
||||
auth_mode: rawFormValue.auth_mode
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SmbClusterTabsComponent } from './smb-cluster-tabs.component';
|
||||
import { RESOURCE_TYPE, SMBCluster } from '../smb.model';
|
||||
import { CLUSTER_RESOURCE, SMBCluster } from '../smb.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('SmbClusterTabsComponent', () => {
|
||||
@ -32,7 +32,7 @@ describe('SmbClusterTabsComponent', () => {
|
||||
|
||||
const selectedSmbCluster = (clusterId: string) => {
|
||||
const smbCluster: SMBCluster = {
|
||||
resource_type: RESOURCE_TYPE,
|
||||
resource_type: CLUSTER_RESOURCE,
|
||||
cluster_id: clusterId,
|
||||
auth_mode: 'user'
|
||||
};
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<cd-table
|
||||
[data]="joinAuth$ | async"
|
||||
columnMode="flex"
|
||||
[columns]="columns"
|
||||
selectionType="single"
|
||||
[hasDetails]="false"
|
||||
(fetchData)="loadJoinAuth()"
|
||||
>
|
||||
</cd-table>
|
||||
@ -0,0 +1,27 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SmbJoinAuthListComponent } from './smb-join-auth-list.component';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { SharedModule } from '~/app/shared/shared.module';
|
||||
|
||||
describe('SmbJoinAuthListComponent', () => {
|
||||
let component: SmbJoinAuthListComponent;
|
||||
let fixture: ComponentFixture<SmbJoinAuthListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SmbJoinAuthListComponent],
|
||||
imports: [SharedModule, HttpClientTestingModule, ToastrModule.forRoot(), RouterTestingModule]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SmbJoinAuthListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,69 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable, BehaviorSubject, of } from 'rxjs';
|
||||
import { switchMap, catchError } from 'rxjs/operators';
|
||||
import { SmbService } from '~/app/shared/api/smb.service';
|
||||
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
|
||||
import { CdTableAction } from '~/app/shared/models/cd-table-action';
|
||||
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
|
||||
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
|
||||
import { Permission } from '~/app/shared/models/permissions';
|
||||
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
|
||||
import { SMBJoinAuth } from '../smb.model';
|
||||
|
||||
@Component({
|
||||
selector: 'cd-smb-join-auth-list',
|
||||
templateUrl: './smb-join-auth-list.component.html',
|
||||
styleUrls: ['./smb-join-auth-list.component.scss']
|
||||
})
|
||||
export class SmbJoinAuthListComponent implements OnInit {
|
||||
columns: CdTableColumn[];
|
||||
permission: Permission;
|
||||
tableActions: CdTableAction[];
|
||||
context: CdTableFetchDataContext;
|
||||
|
||||
joinAuth$: Observable<SMBJoinAuth[]>;
|
||||
subject$ = new BehaviorSubject<SMBJoinAuth[]>([]);
|
||||
|
||||
constructor(
|
||||
private authStorageService: AuthStorageService,
|
||||
public actionLabels: ActionLabelsI18n,
|
||||
private smbService: SmbService
|
||||
) {
|
||||
this.permission = this.authStorageService.getPermissions().smb;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.columns = [
|
||||
{
|
||||
name: $localize`ID`,
|
||||
prop: 'auth_id',
|
||||
flexGrow: 2
|
||||
},
|
||||
{
|
||||
name: $localize`Username`,
|
||||
prop: 'auth.username',
|
||||
flexGrow: 2
|
||||
},
|
||||
{
|
||||
name: $localize`Linked to Cluster`,
|
||||
prop: 'linked_to_cluster',
|
||||
flexGrow: 2
|
||||
}
|
||||
];
|
||||
|
||||
this.joinAuth$ = this.subject$.pipe(
|
||||
switchMap(() =>
|
||||
this.smbService.listJoinAuths().pipe(
|
||||
catchError(() => {
|
||||
this.context.error();
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
loadJoinAuth() {
|
||||
this.subject$.next([]);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<legend
|
||||
*ngIf="selectedTab == Tabs.clusters"
|
||||
i18n
|
||||
>
|
||||
Clusters
|
||||
<cd-help-text>
|
||||
Logical management units that may map to one or more managed Samba service
|
||||
</cd-help-text>
|
||||
</legend>
|
||||
|
||||
<legend
|
||||
*ngIf="selectedTab == Tabs.joinAuths"
|
||||
i18n
|
||||
>
|
||||
Active directory access resources
|
||||
<cd-help-text>
|
||||
Logical management units for authorization on Active Directory servers
|
||||
</cd-help-text>
|
||||
</legend>
|
||||
|
||||
<legend
|
||||
*ngIf="selectedTab == Tabs.usersgroups"
|
||||
i18n
|
||||
>
|
||||
Standalone access resources
|
||||
<cd-help-text>
|
||||
Logical management units for authorization on Standalone servers
|
||||
</cd-help-text>
|
||||
</legend>
|
||||
|
||||
<cds-tabs
|
||||
type="contained"
|
||||
followFocus="true"
|
||||
isNavigation="true"
|
||||
[cacheActive]="false">
|
||||
<cds-tab
|
||||
heading="Clusters"
|
||||
[tabContent]="clusters"
|
||||
i18n-heading
|
||||
(selected)="onSelected(Tabs.clusters)">
|
||||
</cds-tab>
|
||||
<cds-tab
|
||||
heading="Active Directory"
|
||||
[tabContent]="joinAuth"
|
||||
i18n-heading
|
||||
(selected)="onSelected(Tabs.joinAuths)">
|
||||
</cds-tab>
|
||||
<cds-tab
|
||||
heading="Standalone"
|
||||
[tabContent]="usersgroups"
|
||||
i18n-heading
|
||||
(selected)="onSelected(Tabs.usersgroups)">
|
||||
</cds-tab>
|
||||
</cds-tabs>
|
||||
|
||||
<ng-template #clusters>
|
||||
<cd-smb-cluster-list></cd-smb-cluster-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #joinAuth>
|
||||
<cd-smb-join-auth-list></cd-smb-join-auth-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #usersgroups>
|
||||
<cd-smb-users-list></cd-smb-users-list>
|
||||
</ng-template>
|
||||
@ -0,0 +1,38 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SmbTabsComponent } from './smb-tabs.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('SmbTabsComponent', () => {
|
||||
let component: SmbTabsComponent;
|
||||
let fixture: ComponentFixture<SmbTabsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SmbTabsComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SmbTabsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display the heading text in the tab', () => {
|
||||
const tabs = fixture.debugElement.queryAll(By.css('cds-tab'));
|
||||
expect(tabs.length).toBe(3);
|
||||
expect(tabs[0].attributes['heading']).toBe('Clusters');
|
||||
expect(tabs[1].attributes['heading']).toBe('Active Directory');
|
||||
expect(tabs[2].attributes['heading']).toBe('Standalone');
|
||||
});
|
||||
|
||||
// If the tabs cacheActive is set to true data for all tabs will be fetched at once,
|
||||
// smb mgr module might hit recursive error when doing multiple request to the db
|
||||
it('should have cache disabled', () => {
|
||||
const tabs = fixture.nativeElement.querySelector('cds-tabs');
|
||||
expect(tabs.cacheActive).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
enum TABS {
|
||||
'clusters',
|
||||
'joinAuths',
|
||||
'usersgroups'
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cd-smb-tabs',
|
||||
templateUrl: './smb-tabs.component.html',
|
||||
styleUrls: ['./smb-tabs.component.scss']
|
||||
})
|
||||
export class SmbTabsComponent {
|
||||
selectedTab: TABS;
|
||||
|
||||
onSelected(tab: TABS) {
|
||||
this.selectedTab = tab;
|
||||
}
|
||||
|
||||
public get Tabs(): typeof TABS {
|
||||
return TABS;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<cds-tabs
|
||||
type="contained"
|
||||
followFocus="true"
|
||||
isNavigation="true"
|
||||
[cacheActive]="false"
|
||||
>
|
||||
<cds-tab
|
||||
heading="Users"
|
||||
i18n-heading>
|
||||
<cd-table
|
||||
[data]="selection?.values.users"
|
||||
columnMode="flex"
|
||||
[columns]="columns"
|
||||
selectionType="single"
|
||||
[hasDetails]="false"
|
||||
>
|
||||
</cd-table>
|
||||
</cds-tab>
|
||||
</cds-tabs>
|
||||
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SmbUsersgroupsDetailsComponent } from './smb-usersgroups-details.component';
|
||||
|
||||
describe('SmbUsersgroupsDetailsComponent', () => {
|
||||
let component: SmbUsersgroupsDetailsComponent;
|
||||
let fixture: ComponentFixture<SmbUsersgroupsDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SmbUsersgroupsDetailsComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SmbUsersgroupsDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
|
||||
import { SMBUsersGroups } from '../smb.model';
|
||||
|
||||
@Component({
|
||||
selector: 'cd-smb-usersgroups-details',
|
||||
templateUrl: './smb-usersgroups-details.component.html',
|
||||
styleUrls: ['./smb-usersgroups-details.component.scss']
|
||||
})
|
||||
export class SmbUsersgroupsDetailsComponent implements OnInit {
|
||||
@Input()
|
||||
selection: SMBUsersGroups;
|
||||
columns: CdTableColumn[];
|
||||
|
||||
ngOnInit() {
|
||||
this.columns = [
|
||||
{
|
||||
name: $localize`Username`,
|
||||
prop: 'name',
|
||||
flexGrow: 2
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<ng-container *ngIf="usersGroups$ | async as usersGroups">
|
||||
<cd-table
|
||||
[data]="usersGroups"
|
||||
columnMode="flex"
|
||||
[columns]="columns"
|
||||
selectionType="single"
|
||||
[hasDetails]="true"
|
||||
(setExpandedRow)="setExpandedRow($event)"
|
||||
(fetchData)="loadUsersGroups()"
|
||||
>
|
||||
<cd-smb-usersgroups-details
|
||||
*cdTableDetail
|
||||
[selection]="expandedRow"
|
||||
></cd-smb-usersgroups-details>
|
||||
</cd-table>
|
||||
</ng-container>
|
||||
|
||||
<ng-template
|
||||
#groupsNamesTpl
|
||||
let-row="data.row"
|
||||
>
|
||||
<cds-tag
|
||||
*ngFor="let group of row?.values.groups"
|
||||
size="md"
|
||||
>
|
||||
{{ group.name }}
|
||||
</cds-tag>
|
||||
</ng-template>
|
||||
@ -0,0 +1,27 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SmbUsersgroupsListComponent } from './smb-usersgroups-list.component';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { SharedModule } from '~/app/shared/shared.module';
|
||||
|
||||
describe('SmbUsersgroupsListComponent', () => {
|
||||
let component: SmbUsersgroupsListComponent;
|
||||
let fixture: ComponentFixture<SmbUsersgroupsListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SmbUsersgroupsListComponent],
|
||||
imports: [SharedModule, HttpClientTestingModule, ToastrModule.forRoot(), RouterTestingModule]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SmbUsersgroupsListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,83 @@
|
||||
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { catchError, switchMap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
|
||||
import { CdTableAction } from '~/app/shared/models/cd-table-action';
|
||||
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
|
||||
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
|
||||
import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
|
||||
import { Permission } from '~/app/shared/models/permissions';
|
||||
|
||||
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
|
||||
import { SmbService } from '~/app/shared/api/smb.service';
|
||||
import { SMBUsersGroups } from '../smb.model';
|
||||
|
||||
@Component({
|
||||
selector: 'cd-smb-users-list',
|
||||
templateUrl: './smb-usersgroups-list.component.html',
|
||||
styleUrls: ['./smb-usersgroups-list.component.scss']
|
||||
})
|
||||
export class SmbUsersgroupsListComponent extends ListWithDetails implements OnInit {
|
||||
@ViewChild('groupsNamesTpl', { static: true })
|
||||
groupsNamesTpl: TemplateRef<any>;
|
||||
columns: CdTableColumn[];
|
||||
permission: Permission;
|
||||
tableActions: CdTableAction[];
|
||||
context: CdTableFetchDataContext;
|
||||
|
||||
usersGroups$: Observable<SMBUsersGroups[]>;
|
||||
subject$ = new BehaviorSubject<SMBUsersGroups[]>([]);
|
||||
|
||||
constructor(
|
||||
private authStorageService: AuthStorageService,
|
||||
public actionLabels: ActionLabelsI18n,
|
||||
private smbService: SmbService
|
||||
) {
|
||||
super();
|
||||
this.permission = this.authStorageService.getPermissions().smb;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.columns = [
|
||||
{
|
||||
name: $localize`ID`,
|
||||
prop: 'users_groups_id',
|
||||
flexGrow: 2
|
||||
},
|
||||
{
|
||||
name: $localize`Number of users`,
|
||||
prop: 'values.users.length',
|
||||
flexGrow: 2
|
||||
},
|
||||
{
|
||||
name: $localize`Groups`,
|
||||
prop: 'values.groups',
|
||||
cellTemplate: this.groupsNamesTpl,
|
||||
flexGrow: 2
|
||||
},
|
||||
{
|
||||
name: $localize`Linked to`,
|
||||
prop: 'values.linked_to_cluster',
|
||||
flexGrow: 2
|
||||
}
|
||||
];
|
||||
|
||||
this.usersGroups$ = this.subject$.pipe(
|
||||
switchMap(() =>
|
||||
this.smbService.listUsersGroups().pipe(
|
||||
catchError(() => {
|
||||
this.context.error();
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
loadUsersGroups() {
|
||||
this.subject$.next([]);
|
||||
}
|
||||
}
|
||||
@ -52,8 +52,6 @@ export const PLACEMENT = {
|
||||
label: 'label'
|
||||
};
|
||||
|
||||
export const RESOURCE_TYPE = 'ceph.smb.cluster';
|
||||
|
||||
export interface SMBShare {
|
||||
cluster_id: string;
|
||||
share_id: string;
|
||||
@ -79,3 +77,42 @@ interface SMBShareLoginControl {
|
||||
access: 'read' | 'read-write' | 'none' | 'admin';
|
||||
category?: 'user' | 'group';
|
||||
}
|
||||
|
||||
export interface SMBJoinAuth {
|
||||
resource_type: string;
|
||||
auth_id: string;
|
||||
intent: Intent;
|
||||
auth: Auth;
|
||||
linked_to_cluster?: string;
|
||||
}
|
||||
|
||||
export interface SMBUsersGroups {
|
||||
resource_type: string;
|
||||
users_groups_id: string;
|
||||
intent: Intent;
|
||||
values: Value;
|
||||
linked_to_cluster?: string;
|
||||
}
|
||||
|
||||
interface Auth {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Group {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Value {
|
||||
users: User[];
|
||||
groups: Group[];
|
||||
}
|
||||
|
||||
type Intent = 'present' | 'removed';
|
||||
|
||||
export const CLUSTER_RESOURCE = 'ceph.smb.cluster';
|
||||
|
||||
@ -2,7 +2,6 @@ import Close from '@carbon/icons/es/close/32';
|
||||
import { SmbClusterListComponent } from './smb-cluster-list/smb-cluster-list.component';
|
||||
import { SmbClusterFormComponent } from './smb-cluster-form/smb-cluster-form.component';
|
||||
import { AppRoutingModule } from '~/app/app-routing.module';
|
||||
import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
|
||||
import { DataTableModule } from '~/app/shared/datatable/datatable.module';
|
||||
import { SmbDomainSettingModalComponent } from './smb-domain-setting-modal/smb-domain-setting-modal.component';
|
||||
import { SmbClusterTabsComponent } from './smb-cluster-tabs/smb-cluster-tabs.component';
|
||||
@ -21,7 +20,8 @@ import {
|
||||
NumberModule,
|
||||
PlaceholderModule,
|
||||
SelectModule,
|
||||
TabsModule
|
||||
TabsModule,
|
||||
TagModule
|
||||
} from 'carbon-components-angular';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@ -29,6 +29,11 @@ import { SharedModule } from '~/app/shared/shared.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SmbUsersgroupsListComponent } from './smb-usersgroups-list/smb-usersgroups-list.component';
|
||||
import { SmbTabsComponent } from './smb-tabs/smb-tabs.component';
|
||||
import { SmbJoinAuthListComponent } from './smb-join-auth-list/smb-join-auth-list.component';
|
||||
import { SmbUsersgroupsDetailsComponent } from './smb-usersgroups-details/smb-usersgroups-details.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
@ -36,7 +41,6 @@ import { NgModule } from '@angular/core';
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AppRoutingModule,
|
||||
BaseChartDirective,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@ -44,6 +48,7 @@ import { NgModule } from '@angular/core';
|
||||
GridModule,
|
||||
SelectModule,
|
||||
TabsModule,
|
||||
TagModule,
|
||||
InputModule,
|
||||
CheckboxModule,
|
||||
SelectModule,
|
||||
@ -62,9 +67,12 @@ import { NgModule } from '@angular/core';
|
||||
SmbClusterFormComponent,
|
||||
SmbDomainSettingModalComponent,
|
||||
SmbClusterTabsComponent,
|
||||
SmbShareListComponent
|
||||
],
|
||||
providers: [provideCharts(withDefaultRegisterables())]
|
||||
SmbShareListComponent,
|
||||
SmbUsersgroupsListComponent,
|
||||
SmbUsersgroupsDetailsComponent,
|
||||
SmbTabsComponent,
|
||||
SmbJoinAuthListComponent
|
||||
]
|
||||
})
|
||||
export class SmbModule {
|
||||
constructor(private iconService: IconService) {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
|
||||
import { SmbService } from './smb.service';
|
||||
import { configureTestBed } from '~/testing/unit-test-helper';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
describe('SmbService', () => {
|
||||
let service: SmbService;
|
||||
let httpTesting: HttpTestingController;
|
||||
|
||||
configureTestBed({
|
||||
providers: [SmbService],
|
||||
imports: [HttpClientTestingModule]
|
||||
providers: [SmbService, provideHttpClient(), provideHttpClientTesting()]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -46,4 +46,16 @@ describe('SmbService', () => {
|
||||
const req = httpTesting.expectOne('api/smb/share?cluster_id=tango');
|
||||
expect(req.request.method).toBe('GET');
|
||||
});
|
||||
|
||||
it('should call list join auth', () => {
|
||||
service.listJoinAuths().subscribe();
|
||||
const req = httpTesting.expectOne('api/smb/joinauth');
|
||||
expect(req.request.method).toBe('GET');
|
||||
});
|
||||
|
||||
it('should call list usersgroups', () => {
|
||||
service.listUsersGroups().subscribe();
|
||||
const req = httpTesting.expectOne('api/smb/usersgroups');
|
||||
expect(req.request.method).toBe('GET');
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,7 +2,13 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import { DomainSettings, SMBCluster, SMBShare } from '~/app/ceph/smb/smb.model';
|
||||
import {
|
||||
DomainSettings,
|
||||
SMBCluster,
|
||||
SMBJoinAuth,
|
||||
SMBShare,
|
||||
SMBUsersGroups
|
||||
} from '~/app/ceph/smb/smb.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -35,4 +41,12 @@ export class SmbService {
|
||||
listShares(clusterId: string): Observable<SMBShare[]> {
|
||||
return this.http.get<SMBShare[]>(`${this.baseURL}/share?cluster_id=${clusterId}`);
|
||||
}
|
||||
|
||||
listJoinAuths(): Observable<SMBJoinAuth[]> {
|
||||
return this.http.get<SMBJoinAuth[]>(`${this.baseURL}/joinauth`);
|
||||
}
|
||||
|
||||
listUsersGroups(): Observable<SMBUsersGroups[]> {
|
||||
return this.http.get<SMBUsersGroups[]>(`${this.baseURL}/usersgroups`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15012,6 +15012,74 @@ paths:
|
||||
summary: Get an smb cluster
|
||||
tags:
|
||||
- SMB
|
||||
/api/smb/joinauth:
|
||||
get:
|
||||
description: "\n List all smb join auth resources\n\n :return:\
|
||||
\ Returns list of join auth.\n :rtype: List[Dict]\n "
|
||||
parameters:
|
||||
- default: ''
|
||||
in: query
|
||||
name: join_auth
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
schema:
|
||||
items:
|
||||
properties:
|
||||
auth:
|
||||
description: Authentication credentials
|
||||
properties:
|
||||
password:
|
||||
description: Password for authentication
|
||||
type: string
|
||||
username:
|
||||
description: Username for authentication
|
||||
type: string
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
type: object
|
||||
auth_id:
|
||||
description: Unique identifier for the join auth resource
|
||||
type: string
|
||||
intent:
|
||||
description: Desired state of the resource, e.g., 'present'
|
||||
or 'removed'
|
||||
type: string
|
||||
linked_to_cluster:
|
||||
description: Optional string containing a cluster ID. If
|
||||
set, the resource is linked to the cluster and will be automatically
|
||||
removed when the cluster is removed
|
||||
type: string
|
||||
resource_type:
|
||||
description: ceph.smb.join.auth
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- resource_type
|
||||
- auth_id
|
||||
- intent
|
||||
- auth
|
||||
- linked_to_cluster
|
||||
type: array
|
||||
description: OK
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: List smb join authorization resources
|
||||
tags:
|
||||
- SMB
|
||||
/api/smb/share:
|
||||
get:
|
||||
description: "\n List all smb shares or all shares for a given cluster\n\
|
||||
@ -15139,6 +15207,95 @@ paths:
|
||||
summary: Remove an smb share
|
||||
tags:
|
||||
- SMB
|
||||
/api/smb/usersgroups:
|
||||
get:
|
||||
description: "\n List all smb usersgroups resources\n\n :return:\
|
||||
\ Returns list of usersgroups.\n :rtype: List[Dict]\n "
|
||||
parameters:
|
||||
- default: ''
|
||||
in: query
|
||||
name: users_groups
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
schema:
|
||||
items:
|
||||
properties:
|
||||
intent:
|
||||
description: Desired state of the resource, e.g., 'present'
|
||||
or 'removed'
|
||||
type: string
|
||||
linked_to_cluster:
|
||||
description: Optional string containing a cluster ID. If
|
||||
set, the resource is linked to the cluster and will be automatically
|
||||
removed when the cluster is removed
|
||||
type: string
|
||||
resource_type:
|
||||
description: ceph.smb.usersgroups
|
||||
type: string
|
||||
users_groups_id:
|
||||
description: A short string identifying the usersgroups resource
|
||||
type: string
|
||||
values:
|
||||
description: Required object containing users and groups information
|
||||
properties:
|
||||
groups:
|
||||
description: List of group objects, each containing a name
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: The name of the group
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
users:
|
||||
description: List of user objects, each containing a name
|
||||
and password
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: The user name
|
||||
type: string
|
||||
password:
|
||||
description: The password for the user
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- password
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- users
|
||||
- groups
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- resource_type
|
||||
- users_groups_id
|
||||
- intent
|
||||
- values
|
||||
- linked_to_cluster
|
||||
type: array
|
||||
description: OK
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: List smb user resources
|
||||
tags:
|
||||
- SMB
|
||||
/api/summary:
|
||||
get:
|
||||
parameters: []
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import json
|
||||
from unittest.mock import Mock
|
||||
|
||||
from dashboard.controllers.smb import SMBCluster, SMBShare
|
||||
from dashboard.controllers.smb import SMBCluster, SMBJoinAuth, SMBShare, SMBUsersgroups
|
||||
|
||||
from .. import mgr
|
||||
from ..tests import ControllerTestCase
|
||||
@ -219,3 +219,103 @@ class SMBShareTest(ControllerTestCase):
|
||||
self._delete(f'{self._endpoint}/smbCluster1/share1')
|
||||
self.assertStatus(204)
|
||||
mgr.remote.assert_called_once_with('smb', 'apply_resources', json.dumps(_res_simplified))
|
||||
|
||||
|
||||
class SMBJoinAuthTest(ControllerTestCase):
|
||||
_endpoint = '/api/smb/joinauth'
|
||||
|
||||
_join_auths = {
|
||||
"resources": [
|
||||
{
|
||||
"resource_type": "ceph.smb.join.auth",
|
||||
"auth_id": "join1-admin",
|
||||
"intent": "present",
|
||||
"auth": {
|
||||
"username": "Administrator",
|
||||
"password": "Passw0rd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource_type": "ceph.smb.join.auth",
|
||||
"auth_id": "ja2",
|
||||
"intent": "present",
|
||||
"auth": {
|
||||
"username": "user123",
|
||||
"password": "foobar"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_server(cls):
|
||||
cls.setup_controllers([SMBJoinAuth])
|
||||
|
||||
def test_list_one_join_auth(self):
|
||||
mgr.remote = Mock(return_value=self._join_auths['resources'][0])
|
||||
|
||||
self._get(self._endpoint)
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody([self._join_auths['resources'][0]])
|
||||
|
||||
def test_list_multiple_clusters(self):
|
||||
mgr.remote = Mock(return_value=self._join_auths)
|
||||
|
||||
self._get(self._endpoint)
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(self._join_auths['resources'])
|
||||
|
||||
|
||||
class SMBUsersgroupsTest(ControllerTestCase):
|
||||
_endpoint = '/api/smb/usersgroups'
|
||||
|
||||
_usersgroups = {
|
||||
"resources": [
|
||||
{
|
||||
"resource_type": "ceph.smb.usersgroups",
|
||||
"users_groups_id": "u2",
|
||||
"intent": "present",
|
||||
"values": {
|
||||
"users": [
|
||||
{
|
||||
"name": "user3",
|
||||
"password": "pass"
|
||||
}
|
||||
],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource_type": "ceph.smb.usersgroups",
|
||||
"users_groups_id": "u1",
|
||||
"intent": "present",
|
||||
"values": {
|
||||
"users": [
|
||||
{
|
||||
"name": "user2",
|
||||
"password": "pass"
|
||||
}
|
||||
],
|
||||
"groups": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_server(cls):
|
||||
cls.setup_controllers([SMBUsersgroups])
|
||||
|
||||
def test_list_one_usersgroups(self):
|
||||
mgr.remote = Mock(return_value=self._usersgroups['resources'][0])
|
||||
|
||||
self._get(self._endpoint)
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody([self._usersgroups['resources'][0]])
|
||||
|
||||
def test_list_multiple_usersgroups(self):
|
||||
mgr.remote = Mock(return_value=self._usersgroups)
|
||||
|
||||
self._get(self._endpoint)
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(self._usersgroups['resources'])
|
||||
|
||||
Loading…
Reference in New Issue
Block a user