]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / pool / pool-list / pool-list.component.ts
1 import { Component, OnInit, TemplateRef, 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
7 import { ConfigurationService } from '../../../shared/api/configuration.service';
8 import { PoolService } from '../../../shared/api/pool.service';
9 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
10 import { ActionLabelsI18n, URLVerbs } 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 { ViewCacheStatus } from '../../../shared/enum/view-cache-status.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 { ExecutingTask } from '../../../shared/models/executing-task';
18 import { FinishedTask } from '../../../shared/models/finished-task';
19 import { Permissions } from '../../../shared/models/permissions';
20 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
21 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
22 import { TaskListService } from '../../../shared/services/task-list.service';
23 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
24 import { URLBuilderService } from '../../../shared/services/url-builder.service';
25 import { PgCategoryService } from '../../shared/pg-category.service';
26 import { Pool } from '../pool';
27 import { PoolStats } from '../pool-stat';
28
29 const BASE_URL = 'pool';
30
31 @Component({
32 selector: 'cd-pool-list',
33 templateUrl: './pool-list.component.html',
34 providers: [
35 TaskListService,
36 { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }
37 ],
38 styleUrls: ['./pool-list.component.scss']
39 })
40 export class PoolListComponent implements OnInit {
41 @ViewChild(TableComponent)
42 table: TableComponent;
43 @ViewChild('poolUsageTpl')
44 poolUsageTpl: TemplateRef<any>;
45
46 @ViewChild('poolConfigurationSourceTpl')
47 poolConfigurationSourceTpl: TemplateRef<any>;
48
49 pools: Pool[] = [];
50 columns: CdTableColumn[];
51 selection = new CdTableSelection();
52 modalRef: BsModalRef;
53 executingTasks: ExecutingTask[] = [];
54 permissions: Permissions;
55 tableActions: CdTableAction[];
56 viewCacheStatusList: any[];
57 selectionCacheTiers: any[] = [];
58 monAllowPoolDelete = false;
59
60 constructor(
61 private poolService: PoolService,
62 private taskWrapper: TaskWrapperService,
63 private authStorageService: AuthStorageService,
64 private taskListService: TaskListService,
65 private modalService: BsModalService,
66 private i18n: I18n,
67 private pgCategoryService: PgCategoryService,
68 private dimlessPipe: DimlessPipe,
69 private urlBuilder: URLBuilderService,
70 private configurationService: ConfigurationService,
71 public actionLabels: ActionLabelsI18n
72 ) {
73 this.permissions = this.authStorageService.getPermissions();
74 this.tableActions = [
75 {
76 permission: 'create',
77 icon: 'fa-plus',
78 routerLink: () => this.urlBuilder.getCreate(),
79 name: this.actionLabels.CREATE
80 },
81 {
82 permission: 'update',
83 icon: 'fa-pencil',
84 routerLink: () =>
85 this.urlBuilder.getEdit(encodeURIComponent(this.selection.first().pool_name)),
86 name: this.actionLabels.EDIT
87 },
88 {
89 permission: 'delete',
90 icon: 'fa-trash-o',
91 click: () => this.deletePoolModal(),
92 name: this.actionLabels.DELETE,
93 disable: () => !this.selection.first() || !this.monAllowPoolDelete,
94 disableDesc: () => this.getDisableDesc()
95 }
96 ];
97
98 this.configurationService.get('mon_allow_pool_delete').subscribe((data: any) => {
99 if (_.has(data, 'value')) {
100 const monSection = _.find(data.value, (v) => {
101 return v.section === 'mon';
102 }) || { value: false };
103 this.monAllowPoolDelete = monSection.value === 'true' ? true : false;
104 }
105 });
106 }
107
108 ngOnInit() {
109 const compare = (prop: string, pool1: Pool, pool2: Pool) =>
110 _.get(pool1, prop) > _.get(pool2, prop) ? 1 : -1;
111 this.columns = [
112 {
113 prop: 'pool_name',
114 name: this.i18n('Name'),
115 flexGrow: 4,
116 cellTransformation: CellTemplate.executing
117 },
118 {
119 prop: 'type',
120 name: this.i18n('Type'),
121 flexGrow: 2
122 },
123 {
124 prop: 'application_metadata',
125 name: this.i18n('Applications'),
126 flexGrow: 2
127 },
128 {
129 prop: 'pg_status',
130 name: this.i18n('PG Status'),
131 flexGrow: 3,
132 cellClass: ({ row, column, value }): any => {
133 return this.getPgStatusCellClass(row, column, value);
134 }
135 },
136 {
137 prop: 'size',
138 name: this.i18n('Replica Size'),
139 flexGrow: 1,
140 cellClass: 'text-right'
141 },
142 {
143 prop: 'last_change',
144 name: this.i18n('Last Change'),
145 flexGrow: 1,
146 cellClass: 'text-right'
147 },
148 {
149 prop: 'erasure_code_profile',
150 name: this.i18n('Erasure Coded Profile'),
151 flexGrow: 2
152 },
153 {
154 prop: 'crush_rule',
155 name: this.i18n('Crush Ruleset'),
156 flexGrow: 3
157 },
158 {
159 name: this.i18n('Usage'),
160 prop: 'usage',
161 cellTemplate: this.poolUsageTpl,
162 flexGrow: 3
163 },
164 {
165 prop: 'stats.rd_bytes.rates',
166 name: this.i18n('Read bytes'),
167 comparator: (_valueA, _valueB, rowA: Pool, rowB: Pool) =>
168 compare('stats.rd_bytes.latest', rowA, rowB),
169 cellTransformation: CellTemplate.sparkline,
170 flexGrow: 3
171 },
172 {
173 prop: 'stats.wr_bytes.rates',
174 name: this.i18n('Write bytes'),
175 comparator: (_valueA, _valueB, rowA: Pool, rowB: Pool) =>
176 compare('stats.wr_bytes.latest', rowA, rowB),
177 cellTransformation: CellTemplate.sparkline,
178 flexGrow: 3
179 },
180 {
181 prop: 'stats.rd.rate',
182 name: this.i18n('Read ops'),
183 flexGrow: 1,
184 pipe: this.dimlessPipe,
185 cellTransformation: CellTemplate.perSecond
186 },
187 {
188 prop: 'stats.wr.rate',
189 name: this.i18n('Write ops'),
190 flexGrow: 1,
191 pipe: this.dimlessPipe,
192 cellTransformation: CellTemplate.perSecond
193 }
194 ];
195
196 this.taskListService.init(
197 () => this.poolService.getList(),
198 undefined,
199 (pools) => (this.pools = this.transformPoolsData(pools)),
200 () => {
201 this.table.reset(); // Disable loading indicator.
202 this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }];
203 },
204 (task) => task.name.startsWith(`${BASE_URL}/`),
205 (pool, task) => task.metadata['pool_name'] === pool.pool_name,
206 { default: (task: ExecutingTask) => new Pool(task.metadata['pool_name']) }
207 );
208 }
209
210 updateSelection(selection: CdTableSelection) {
211 this.selection = selection;
212 this.getSelectionTiers();
213 }
214
215 deletePoolModal() {
216 const name = this.selection.first().pool_name;
217 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
218 initialState: {
219 itemDescription: 'Pool',
220 submitActionObservable: () =>
221 this.taskWrapper.wrapTaskAroundCall({
222 task: new FinishedTask(`${BASE_URL}/${URLVerbs.DELETE}`, { pool_name: name }),
223 call: this.poolService.delete(name)
224 })
225 }
226 });
227 }
228
229 getPgStatusCellClass(_row, _column, value): object {
230 return {
231 'text-right': true,
232 [`pg-${this.pgCategoryService.getTypeByStates(value)}`]: true
233 };
234 }
235
236 transformPoolsData(pools: any) {
237 const requiredStats = ['bytes_used', 'max_avail', 'rd_bytes', 'wr_bytes', 'rd', 'wr'];
238 const emptyStat = { latest: 0, rate: 0, rates: [] };
239
240 _.forEach(pools, (pool: Pool) => {
241 pool['pg_status'] = this.transformPgStatus(pool['pg_status']);
242 const stats: PoolStats = {};
243 _.forEach(requiredStats, (stat) => {
244 stats[stat] = pool.stats && pool.stats[stat] ? pool.stats[stat] : emptyStat;
245 });
246 pool['stats'] = stats;
247 const avail = stats.bytes_used.latest + stats.max_avail.latest;
248 pool['usage'] = avail > 0 ? stats.bytes_used.latest / avail : avail;
249
250 ['rd_bytes', 'wr_bytes'].forEach((stat) => {
251 pool.stats[stat].rates = pool.stats[stat].rates.map((point) => point[1]);
252 });
253 pool.cdIsBinary = true;
254 });
255
256 return pools;
257 }
258
259 transformPgStatus(pgStatus: any): string {
260 const strings = [];
261 _.forEach(pgStatus, (count, state) => {
262 strings.push(`${count} ${state}`);
263 });
264
265 return strings.join(', ');
266 }
267
268 getPoolDetails(pool: object) {
269 return _.omit(pool, ['cdExecuting', 'cdIsBinary']);
270 }
271
272 getSelectionTiers() {
273 const cacheTierIds = this.selection.hasSingleSelection ? this.selection.first()['tiers'] : [];
274 this.selectionCacheTiers = this.pools.filter((pool) => cacheTierIds.includes(pool.pool));
275 }
276
277 getDisableDesc(): string | undefined {
278 if (!this.monAllowPoolDelete) {
279 return this.i18n(
280 'Pool deletion is disabled by the mon_allow_pool_delete configuration setting.'
281 );
282 }
283 }
284 }