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