]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
f3911d58a9dc889f095724d56e9971e1cce0f717
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / iscsi-target-list / iscsi-target-list.component.ts
1 import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
2
3 import { I18n } from '@ngx-translate/i18n-polyfill';
4 import * as _ from 'lodash';
5 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
6 import { Subscription } from 'rxjs';
7
8 import { IscsiService } from '../../../shared/api/iscsi.service';
9 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
10 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
11 import { TableComponent } from '../../../shared/datatable/table/table.component';
12 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
13 import { Icons } from '../../../shared/enum/icons.enum';
14 import { CdTableAction } from '../../../shared/models/cd-table-action';
15 import { CdTableColumn } from '../../../shared/models/cd-table-column';
16 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
17 import { FinishedTask } from '../../../shared/models/finished-task';
18 import { Permission } from '../../../shared/models/permissions';
19 import { Task } from '../../../shared/models/task';
20 import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
21 import { NotAvailablePipe } from '../../../shared/pipes/not-available.pipe';
22 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
23 import { SummaryService } from '../../../shared/services/summary.service';
24 import { TaskListService } from '../../../shared/services/task-list.service';
25 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
26 import { IscsiTargetDiscoveryModalComponent } from '../iscsi-target-discovery-modal/iscsi-target-discovery-modal.component';
27
28 @Component({
29 selector: 'cd-iscsi-target-list',
30 templateUrl: './iscsi-target-list.component.html',
31 styleUrls: ['./iscsi-target-list.component.scss'],
32 providers: [TaskListService]
33 })
34 export class IscsiTargetListComponent implements OnInit, OnDestroy {
35 @ViewChild(TableComponent, { static: false })
36 table: TableComponent;
37
38 available: boolean = undefined;
39 columns: CdTableColumn[];
40 docsUrl: string;
41 modalRef: BsModalRef;
42 permission: Permission;
43 selection = new CdTableSelection();
44 cephIscsiConfigVersion: number;
45 settings: any;
46 status: string;
47 summaryDataSubscription: Subscription;
48 tableActions: CdTableAction[];
49 targets: any[] = [];
50 icons = Icons;
51
52 builders = {
53 'iscsi/target/create': (metadata: object) => {
54 return {
55 target_iqn: metadata['target_iqn']
56 };
57 }
58 };
59
60 constructor(
61 private authStorageService: AuthStorageService,
62 private i18n: I18n,
63 private iscsiService: IscsiService,
64 private taskListService: TaskListService,
65 private cephReleaseNamePipe: CephReleaseNamePipe,
66 private notAvailablePipe: NotAvailablePipe,
67 private summaryservice: SummaryService,
68 private modalService: BsModalService,
69 private taskWrapper: TaskWrapperService,
70 public actionLabels: ActionLabelsI18n
71 ) {
72 this.permission = this.authStorageService.getPermissions().iscsi;
73
74 this.tableActions = [
75 {
76 permission: 'create',
77 icon: Icons.add,
78 routerLink: () => '/block/iscsi/targets/create',
79 name: this.actionLabels.CREATE
80 },
81 {
82 permission: 'update',
83 icon: Icons.edit,
84 routerLink: () => `/block/iscsi/targets/edit/${this.selection.first().target_iqn}`,
85 name: this.actionLabels.EDIT,
86 disable: () => !this.selection.first() || !_.isUndefined(this.getDeleteDisableDesc()),
87 disableDesc: () => this.getEditDisableDesc()
88 },
89 {
90 permission: 'delete',
91 icon: Icons.destroy,
92 click: () => this.deleteIscsiTargetModal(),
93 name: this.actionLabels.DELETE,
94 disable: () => !this.selection.first() || !_.isUndefined(this.getDeleteDisableDesc()),
95 disableDesc: () => this.getDeleteDisableDesc()
96 }
97 ];
98 }
99
100 ngOnInit() {
101 this.columns = [
102 {
103 name: this.i18n('Target'),
104 prop: 'target_iqn',
105 flexGrow: 2,
106 cellTransformation: CellTemplate.executing
107 },
108 {
109 name: this.i18n('Portals'),
110 prop: 'cdPortals',
111 flexGrow: 2
112 },
113 {
114 name: this.i18n('Images'),
115 prop: 'cdImages',
116 flexGrow: 2
117 },
118 {
119 name: this.i18n('# Sessions'),
120 prop: 'info.num_sessions',
121 pipe: this.notAvailablePipe,
122 flexGrow: 1
123 }
124 ];
125
126 this.iscsiService.status().subscribe((result: any) => {
127 this.available = result.available;
128
129 if (result.available) {
130 this.iscsiService.version().subscribe((res: any) => {
131 this.cephIscsiConfigVersion = res['ceph_iscsi_config_version'];
132 this.taskListService.init(
133 () => this.iscsiService.listTargets(),
134 (resp) => this.prepareResponse(resp),
135 (targets) => (this.targets = targets),
136 () => this.onFetchError(),
137 this.taskFilter,
138 this.itemFilter,
139 this.builders
140 );
141 });
142
143 this.iscsiService.settings().subscribe((settings: any) => {
144 this.settings = settings;
145 });
146 } else {
147 const summary = this.summaryservice.getCurrentSummary();
148 const releaseName = this.cephReleaseNamePipe.transform(summary.version);
149 this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-iscsi-management`;
150 this.status = result.message;
151 }
152 });
153 }
154
155 ngOnDestroy() {
156 if (this.summaryDataSubscription) {
157 this.summaryDataSubscription.unsubscribe();
158 }
159 }
160
161 getEditDisableDesc(): string | undefined {
162 const first = this.selection.first();
163 if (first && first.cdExecuting) {
164 return first.cdExecuting;
165 }
166 if (first && _.isUndefined(first['info'])) {
167 return this.i18n('Unavailable gateway(s)');
168 }
169
170 return undefined;
171 }
172
173 getDeleteDisableDesc(): string | undefined {
174 const first = this.selection.first();
175 if (first && first.cdExecuting) {
176 return first.cdExecuting;
177 }
178 if (first && _.isUndefined(first['info'])) {
179 return this.i18n('Unavailable gateway(s)');
180 }
181 if (first && first['info'] && first['info']['num_sessions']) {
182 return this.i18n('Target has active sessions');
183 }
184
185 return undefined;
186 }
187
188 prepareResponse(resp: any): any[] {
189 resp.forEach((element: Record<string, any>) => {
190 element.cdPortals = element.portals.map(
191 (portal: Record<string, any>) => `${portal.host}:${portal.ip}`
192 );
193 element.cdImages = element.disks.map(
194 (disk: Record<string, any>) => `${disk.pool}/${disk.image}`
195 );
196 });
197
198 return resp;
199 }
200
201 onFetchError() {
202 this.table.reset(); // Disable loading indicator.
203 }
204
205 itemFilter(entry: Record<string, any>, task: Task) {
206 return entry.target_iqn === task.metadata['target_iqn'];
207 }
208
209 taskFilter(task: Task) {
210 return ['iscsi/target/create', 'iscsi/target/edit', 'iscsi/target/delete'].includes(task.name);
211 }
212
213 updateSelection(selection: CdTableSelection) {
214 this.selection = selection;
215 }
216
217 deleteIscsiTargetModal() {
218 const target_iqn = this.selection.first().target_iqn;
219
220 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
221 initialState: {
222 itemDescription: this.i18n('iSCSI target'),
223 itemNames: [target_iqn],
224 submitActionObservable: () =>
225 this.taskWrapper.wrapTaskAroundCall({
226 task: new FinishedTask('iscsi/target/delete', {
227 target_iqn: target_iqn
228 }),
229 call: this.iscsiService.deleteTarget(target_iqn)
230 })
231 }
232 });
233 }
234
235 configureDiscoveryAuth() {
236 this.modalService.show(IscsiTargetDiscoveryModalComponent, {});
237 }
238 }