]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add rbd list sorting support
authorPere Diaz Bou <pdiazbou@redhat.com>
Mon, 4 Jul 2022 14:57:43 +0000 (16:57 +0200)
committerPere Diaz Bou <pdiazbou@redhat.com>
Tue, 12 Jul 2022 17:09:02 +0000 (19:09 +0200)
Support sorting with name, pool name and namespace

Signed-off-by: Pere Diaz Bou <pdiazbou@redhat.com>
qa/tasks/mgr/dashboard/test_rbd.py
src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-fetch-data-context.ts
src/pybind/mgr/dashboard/services/rbd.py

index 63e7ac094ef0d4a231e60c2ade1f745217b6ecf4..07d0f3cb961ce0ea2ea5b4de6813034f0152d404 100644 (file)
@@ -13,7 +13,7 @@ class RbdTest(DashboardTestCase):
 
     @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['create', 'update', 'delete']}])
     def test_read_access_permissions(self):
-        self._get('/api/block/image')
+        self._get('/api/block/image?offset=0&limit=5&search=&sort=%3Cname')
         self.assertStatus(403)
         self.get_image('pool', None, 'image')
         self.assertStatus(403)
index 4376cc3f05aa718abacdc70bb9f57b368230c6f6..9cc5df56d3cce3b94db3af9aa180be72f06c2ae8 100644 (file)
@@ -79,14 +79,14 @@ class Rbd(RESTController):
     ALLOW_DISABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "deep-flatten",
                               "journaling"}
 
-    def _rbd_list(self, pool_name=None, offset=0, limit=5, search=''):
+    def _rbd_list(self, pool_name=None, offset=0, limit=5, search='', sort=''):
         if pool_name:
             pools = [pool_name]
         else:
             pools = [p['pool_name'] for p in CephService.get_pool_list('rbd')]
 
         images, num_total_images = RbdService.rbd_pool_list(
-            pools, offset=offset, limit=limit, search=search)
+            pools, offset=offset, limit=limit, search=search, sort=sort)
         cherrypy.response.headers['X-Total-Count'] = num_total_images
         pool_result = {}
         for i, image in enumerate(images):
@@ -110,8 +110,8 @@ class Rbd(RESTController):
                  responses={200: RBD_SCHEMA})
     @RESTController.MethodMap(version=APIVersion(2, 0))  # type: ignore
     def list(self, pool_name=None, offset: int = 0, limit: int = 5,
-             search: str = ''):
-        return self._rbd_list(pool_name, offset=offset, limit=limit, search=search)
+             search: str = '', sort: str = ''):
+        return self._rbd_list(pool_name, offset=offset, limit=limit, search=search, sort=sort)
 
     @handle_rbd_error()
     @handle_rados_error('pool')
index 71d71218486959b9c4d42676c598194ae32a804d..983f39ed9be523e7ef53bad5d6f1841cc1a4f558 100644 (file)
@@ -243,13 +243,11 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
       {
         name: $localize`Pool`,
         prop: 'pool_name',
-        sortable: false,
         flexGrow: 2
       },
       {
         name: $localize`Namespace`,
         prop: 'namespace',
-        sortable: false,
         flexGrow: 2
       },
       {
@@ -375,6 +373,9 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
     if (context !== null) {
       this.tableContext = context;
     }
+    if (this.tableContext == null) {
+      this.tableContext = new CdTableFetchDataContext(() => undefined);
+    }
     return this.rbdService.list(this.tableContext?.toParams());
   }
 
index dc51614ffedd7968f0ca2071e782354e76dafd2c..f5ab8615ac65d1985250267dadd11be1633492af 100644 (file)
@@ -59,7 +59,7 @@ describe('RbdService', () => {
     /* tslint:disable:no-empty */
     const context = new CdTableFetchDataContext(() => {});
     service.list(context.toParams()).subscribe();
-    const req = httpTesting.expectOne('api/block/image?offset=0&limit=10');
+    const req = httpTesting.expectOne('api/block/image?offset=0&limit=10?search=&sort=<name');
     expect(req.request.method).toBe('GET');
   });
 
index 2c19dfbd237f13f7faa5f7d559dcc8d605af63af..8a65e62ae96533e09eca32ae74f63cea049e6599 100644 (file)
                  [footerHeight]="footer ? 'auto' : 0"
                  [count]="count"
                  [externalPaging]="serverSide"
+                 [externalSorting]="serverSide"
                  [limit]="userConfig.limit > 0 ? userConfig.limit : undefined"
                  [offset]="userConfig.offset >= 0 ? userConfig.offset : 0"
                  (page)="changePage($event)"
index 2ee7ac78bf6ddbb02d3964e093b5fd7d16c7a938..734118632ae9ac5bd1d38d2a52f054f37cebcf41 100644 (file)
@@ -656,6 +656,10 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
       context.pageInfo.offset = this.userConfig.offset;
       context.pageInfo.limit = this.userConfig.limit;
       context.search = this.userConfig.search;
+      if (this.userConfig.sorts?.length) {
+        const sort = this.userConfig.sorts[0];
+        context.sort = `${sort.dir == 'desc' ? '<' : '>'}${sort.prop}`;
+      }
       this.fetchData.emit(context);
       this.updating = true;
     }
@@ -794,6 +798,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
 
   changeSorting({ sorts }: any) {
     this.userConfig.sorts = sorts;
+    if (this.serverSide) {
+      this.reloadData();
+    }
   }
 
   onClearSearch() {
index 56efadeb2baeab64d5b899588774c26b7e1c70eb..7f50bb7f1ec9107943ad2416cd468ade16559295 100644 (file)
@@ -16,6 +16,7 @@ export class CdTableFetchDataContext {
   error: Function;
   pageInfo: PageInfo = new PageInfo();
   search = '';
+  sort = '>name';
 
   constructor(error: () => void) {
     this.error = error;
@@ -25,14 +26,18 @@ export class CdTableFetchDataContext {
     if (this.pageInfo.limit === null) {
       this.pageInfo.limit = 0;
     }
-    if (this.search === null) {
+    if (!this.search) {
       this.search = '';
     }
+    if (!this.sort) {
+      this.sort = '>name';
+    }
     return new HttpParams({
       fromObject: {
         offset: String(this.pageInfo.offset * this.pageInfo.limit),
         limit: String(this.pageInfo.limit),
-        search: this.search
+        search: this.search,
+        sort: this.sort
       }
     });
   }
index db88fc606006f1ba2a8f260188da2c914c3c7360..21ecd34f15769258dda5f46fcc3e646c2ae43ef3 100644 (file)
@@ -9,6 +9,7 @@ import rbd
 
 from .. import mgr
 from ..exceptions import DashboardException
+from ..plugins.ttl_cache import ttl_cache
 from .ceph_service import CephService
 
 try:
@@ -382,6 +383,7 @@ class RbdService(object):
         return stat_parent
 
     @classmethod
+    @ttl_cache(10)
     def _rbd_image_refs(cls, ioctx):
         rbd_inst = rbd.RBD()
         return rbd_inst.list2(ioctx)
@@ -428,7 +430,7 @@ class RbdService(object):
         return joint_refs
 
     @classmethod
-    def rbd_pool_list(cls, pool_names: List[str], namespace=None, offset=0, limit=0, search=''):
+    def rbd_pool_list(cls, pool_names: List[str], namespace=None, offset=0, limit=0, search='', sort=''):
         offset = int(offset)
         limit = int(limit)
         # let's use -1 to denotate we want ALL images for now. Iscsi currently gathers
@@ -442,12 +444,23 @@ class RbdService(object):
         for ref in refs:
             if search in ref['name']:
                 image_refs.append(ref)
+            elif search in ref['pool']:
+                image_refs.append(ref)
+            elif search in ref['namespace']:
+                image_refs.append(ref)
 
         result = []
         end = offset + limit
+        descending = sort[0] == '<'
+        sort_by = sort[1:]
+        if sort_by == 'pool_name':
+            sort_by = 'pool'
+        if sort_by not in ['name', 'pool', 'namespace']:
+            sort_by = 'name'
         if limit == -1:
             end = len(image_refs)
-        for image_ref in sorted(image_refs, key=lambda v: v['name'])[offset:end]:
+        for image_ref in sorted(image_refs, key=lambda v: v[sort_by],
+                                reverse=descending)[offset:end]:
             with mgr.rados.open_ioctx(image_ref['pool']) as ioctx:
                 ioctx.set_namespace(image_ref['namespace'])
                 try: