]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: added snap-schedule api and ui list
authorIvo Almeida <ialmeida@redhat.com>
Sat, 25 Nov 2023 19:10:35 +0000 (19:10 +0000)
committerIvo Almeida <ialmeida@redhat.com>
Wed, 14 Feb 2024 13:30:57 +0000 (13:30 +0000)
Fixes: https://tracker.ceph.com/issues/63767
Signed-off-by: Ivo Almeida <ialmeida@redhat.com>
(cherry picked from commit acaad3555414aa1f1b4fa732af70612d50c5e883)

src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/models/snapshot-schedule.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/openapi.yaml

index 7dc8b4e9b570aa070540c89461c176904cd68da4..3014ef704fd020159110d39e809677dfdab3c8a7 100644 (file)
@@ -23,6 +23,11 @@ GET_QUOTAS_SCHEMA = {
     'max_bytes': (int, ''),
     'max_files': (int, '')
 }
+GET_STATFS_SCHEMA = {
+    'bytes': (int, ''),
+    'files': (int, ''),
+    'subdirs': (int, '')
+}
 
 logger = logging.getLogger("controllers.rgw")
 
@@ -331,13 +336,16 @@ class CephFS(RESTController):
 
         standby_table = self.get_standby_table(fsmap['standbys'], mds_versions)
 
+        flags = mdsmap['flags_state']
+
         return {
             "cephfs": {
                 "id": fs_id,
                 "name": mdsmap['fs_name'],
                 "client_count": client_count,
                 "ranks": rank_table,
-                "pools": pools_table
+                "pools": pools_table,
+                "flags": flags,
             },
             "standbys": standby_table,
             "versions": mds_versions
@@ -360,7 +368,7 @@ class CephFS(RESTController):
                                      "No cephfs with id {0}".format(fs_id))
 
         # Decorate the metadata with some fields that will be
-        # indepdendent of whether it's a kernel or userspace
+        # independent of whether it's a kernel or userspace
         # client, so that the javascript doesn't have to grok that.
         for client in clients:
             if "ceph_version" in client['client_metadata']:  # pragma: no cover - no complexity
@@ -519,6 +527,47 @@ class CephFS(RESTController):
         cfs = self._cephfs_instance(fs_id)
         return cfs.get_quotas(path)
 
+    @RESTController.Resource('POST', path='/write_to_file')
+    @allow_empty_body
+    def write_to_file(self, fs_id, path, buf) -> None:
+        """
+        Write some data to the specified path.
+        :param fs_id: The filesystem identifier.
+        :param path: The path of the file to write.
+        :param buf: The str to write to the buf.
+        """
+        cfs = self._cephfs_instance(fs_id)
+        cfs.write_to_file(path, buf)
+
+    @RESTController.Resource('DELETE', path='/unlink')
+    def unlink(self, fs_id, path) -> None:
+        """
+        Removes a file, link, or symbolic link.
+        :param fs_id: The filesystem identifier.
+        :param path: The path of the file or link to unlink.
+        """
+        cfs = self._cephfs_instance(fs_id)
+        cfs.unlink(path)
+
+    @RESTController.Resource('GET', path='/statfs')
+    @EndpointDoc("Get Cephfs statfs of the specified path",
+                 parameters={
+                     'fs_id': (str, 'File System Identifier'),
+                     'path': (str, 'File System Path'),
+                 },
+                 responses={200: GET_STATFS_SCHEMA})
+    def statfs(self, fs_id, path) -> dict:
+        """
+        Get the statfs of the specified path.
+        :param fs_id: The filesystem identifier.
+        :param path: The path of the directory/file.
+        :return: Returns a dictionary containing 'bytes',
+        'files' and 'subdirs'.
+        :rtype: dict
+        """
+        cfs = self._cephfs_instance(fs_id)
+        return cfs.statfs(path)
+
     @RESTController.Resource('POST', path='/snapshot')
     @allow_empty_body
     def snapshot(self, fs_id, path, name=None):
@@ -561,7 +610,11 @@ class CephFSClients(object):
 
     @ViewCache()
     def get(self):
-        return CephService.send_command('mds', 'session ls', srv_spec='{0}:0'.format(self.fscid))
+        try:
+            ret = CephService.send_command('mds', 'session ls', srv_spec='{0}:0'.format(self.fscid))
+        except RuntimeError:
+            ret = []
+        return ret
 
 
 @UIRouter('/cephfs', Scope.CEPHFS)
@@ -886,3 +939,32 @@ class CephFsSnapshotClone(RESTController):
                 f'Failed to create clone {clone_name}: {err}'
             )
         return f'Clone {clone_name} created successfully'
+
+@APIRouter('/cephfs/snaphost/schedule', Scope.CEPHFS)
+@APIDoc("Cephfs Snapshot Scheduling API", "CephFSSnapshotSchedule")
+class CephFSSnapshotSchedule(RESTController):
+
+    def list(self, fs: str, path: str = '/', recursive: bool = True):
+        error_code, out, err = mgr.remote('snap_schedule', 'snap_schedule_list',
+                                          path, recursive, fs, 'plain')
+
+        if len(out) == 0:
+            return []
+
+        snapshot_schedule_list = out.split('\n')
+        output = []
+
+        for snap in snapshot_schedule_list:
+            current_path = snap.strip().split(' ')[0]
+            error_code, status_out, err = mgr.remote('snap_schedule', 'snap_schedule_get',
+                                                     current_path, fs, 'plain')
+            output.append(json.loads(status_out))
+
+        output_json = json.dumps(output)
+
+        if error_code != 0:
+            raise DashboardException(
+                f'Failed to get list of snapshot schedules for path {path}: {err}'
+            )
+
+        return json.loads(output_json)
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html
new file mode 100644 (file)
index 0000000..2e27005
--- /dev/null
@@ -0,0 +1,58 @@
+<ng-container *ngIf="isLoading$ | async">
+  <cd-loading-panel>
+    <span i18n>Loading snapshot schedules...</span>
+  </cd-loading-panel>
+</ng-container>
+
+<ng-template #pathTpl
+             let-row="row">
+  <span
+    class="fw-bold"
+    [ngbTooltip]="fullpathTpl"
+    triggers="click:blur">{{row.path | path}}</span>
+
+  <span *ngIf="row.active; else inactiveStatusTpl">
+    <i [ngClass]="[icons.success, icons.large]"
+       ngbTooltip="{{row.path}} is active"
+       class="text-success"></i>
+  </span>
+
+  <ng-template #inactiveStatusTpl>
+    <i [ngClass]="[icons.warning, icons.large]"
+       class="text-warning"
+       ngbTooltip="{{row.path}} has been deactivated"></i>
+  </ng-template>
+
+  <ng-template #fullpathTpl>
+  <span data-toggle="tooltip"
+        [title]="row.path"
+        class="font-monospace">{{ row.path }}
+    <cd-copy-2-clipboard-button *ngIf="row.path"
+                                [source]="row.path"
+                                [byId]="false"
+                                [showIconOnly]="true">
+    </cd-copy-2-clipboard-button>
+  </span>
+</ng-template>
+
+</ng-template>
+
+<cd-table
+  [data]="snapshotSchedules$ | async"
+  columnMode="flex"
+  [columns]="columns"
+  selectionType="single"
+  [hasDetails]="false"
+  (fetchData)="fetchData()"
+  (updateSelection)="updateSelection($event)"
+>
+  <div class="table-actions btn-toolbar">
+    <cd-table-actions
+      [permission]="permissions.cephfs"
+      [selection]="selection"
+      class="btn-group"
+      [tableActions]="tableActions"
+    >
+    </cd-table-actions>
+  </div>
+</cd-table>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.spec.ts
new file mode 100644 (file)
index 0000000..a20972f
--- /dev/null
@@ -0,0 +1,30 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CephfsSnapshotscheduleListComponent } from './cephfs-snapshotschedule-list.component';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { SharedModule } from '~/app/shared/shared.module';
+import { ToastrModule } from 'ngx-toastr';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { configureTestBed } from '~/testing/unit-test-helper';
+
+describe('CephfsSnapshotscheduleListComponent', () => {
+  let component: CephfsSnapshotscheduleListComponent;
+  let fixture: ComponentFixture<CephfsSnapshotscheduleListComponent>;
+
+  configureTestBed({
+    declarations: [CephfsSnapshotscheduleListComponent],
+    imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule],
+    providers: [NgbActiveModal]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CephfsSnapshotscheduleListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts
new file mode 100644 (file)
index 0000000..d5f24e8
--- /dev/null
@@ -0,0 +1,99 @@
+import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { finalize, shareReplay, switchMap } from 'rxjs/operators';
+import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service';
+import { CdForm } from '~/app/shared/forms/cd-form';
+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 { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { Permissions } from '~/app/shared/models/permissions';
+import { SnapshotSchedule } from '~/app/shared/models/snapshot-schedule';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+
+@Component({
+  selector: 'cd-cephfs-snapshotschedule-list',
+  templateUrl: './cephfs-snapshotschedule-list.component.html',
+  styleUrls: ['./cephfs-snapshotschedule-list.component.scss']
+})
+export class CephfsSnapshotscheduleListComponent extends CdForm implements OnInit, OnChanges {
+  @Input() fsName!: string;
+
+  @ViewChild('pathTpl', { static: true })
+  pathTpl: any;
+
+  snapshotSchedules$!: Observable<SnapshotSchedule[]>;
+  subject$ = new BehaviorSubject<SnapshotSchedule[]>([]);
+  isLoading$ = new BehaviorSubject<boolean>(true);
+  columns: CdTableColumn[] = [];
+  tableActions: CdTableAction[] = [];
+  context!: CdTableFetchDataContext;
+  selection = new CdTableSelection();
+  permissions!: Permissions;
+  modalRef!: NgbModalRef;
+  errorMessage: string = '';
+  selectedName: string = '';
+  icons = Icons;
+
+  constructor(
+    private snapshotScheduleService: CephfsSnapshotScheduleService,
+    private authStorageService: AuthStorageService,
+    private modalService: ModalService
+  ) {
+    super();
+    this.permissions = this.authStorageService.getPermissions();
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.fsName) {
+      this.subject$.next([]);
+    }
+  }
+
+  ngOnInit(): void {
+    this.snapshotSchedules$ = this.subject$.pipe(
+      switchMap(() =>
+        this.snapshotScheduleService
+          .getSnapshotScheduleList('/', this.fsName)
+          .pipe(finalize(() => this.isLoading$.next(false)))
+      ),
+      shareReplay(1)
+    );
+
+    this.columns = [
+      { prop: 'path', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl },
+      { prop: 'subvol', name: $localize`Subvolume` },
+      { prop: 'schedule', name: $localize`Repeat interval` },
+      { prop: 'retention', name: $localize`Retention policy` },
+      { prop: 'created_count', name: $localize`Created Count` },
+      { prop: 'pruned_count', name: $localize`Deleted Count` },
+      { prop: 'start', name: $localize`Start time`, cellTransformation: CellTemplate.timeAgo },
+      { prop: 'created', name: $localize`Created`, cellTransformation: CellTemplate.timeAgo }
+    ];
+
+    this.tableActions = [];
+  }
+
+  fetchData() {
+    this.subject$.next([]);
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+
+  openModal(edit = false) {
+    this.modalService.show(
+      {},
+      {
+        fsName: 'fs1',
+        isEdit: edit
+      },
+      { size: 'lg' }
+    );
+  }
+}
index e032634346326483ebaef4dcf43dfa644bd0ce80..a840692ed7673dc29ab216fee9984711061a2198 100644 (file)
@@ -1,15 +1,17 @@
 <ng-container *ngIf="selection">
-  <nav ngbNav
-       #nav="ngbNav"
-       (navChange)="softRefresh()"
-       class="nav-tabs"
-       cdStatefulTab="cephfs-tabs">
+  <nav
+    ngbNav
+    #nav="ngbNav"
+    (navChange)="softRefresh()"
+    class="nav-tabs"
+    cdStatefulTab="cephfs-tabs"
+  >
     <ng-container ngbNavItem="details">
-      <a ngbNavLink
-         i18n>Details</a>
+      <a
+        ngbNavLink
+        i18n>Details</a>
       <ng-template ngbNavContent>
-        <cd-cephfs-detail [data]="details">
-        </cd-cephfs-detail>
+        <cd-cephfs-detail [data]="details"> </cd-cephfs-detail>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="directories">
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="subvolumes">
-      <a ngbNavLink
-         i18n>Subvolumes</a>
+      <a
+      ngbNavLink
+      i18n>Subvolumes</a>
       <ng-template ngbNavContent>
-        <cd-cephfs-subvolume-list [fsName]="selection.mdsmap.fs_name"
-                                  [pools]="details.pools"></cd-cephfs-subvolume-list>
+        <cd-cephfs-subvolume-list
+          [fsName]="selection.mdsmap.fs_name"
+          [pools]="details.pools"
+        ></cd-cephfs-subvolume-list>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="subvolume-groups">
-      <a ngbNavLink
-         i18n>Subvolume groups</a>
+      <a
+      ngbNavLink
+      i18n>Subvolume groups</a>
       <ng-template ngbNavContent>
-        <cd-cephfs-subvolume-group [fsName]="selection.mdsmap.fs_name"
-                                   [pools]="details.pools">
+        <cd-cephfs-subvolume-group
+        [fsName]="selection.mdsmap.fs_name"
+        [pools]="details.pools">
         </cd-cephfs-subvolume-group>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="snapshots">
-      <a ngbNavLink
-         i18n>Snapshots</a>
+      <a
+      ngbNavLink
+      i18n>Snapshots</a>
       <ng-template ngbNavContent>
         <cd-cephfs-subvolume-snapshots-list [fsName]="selection.mdsmap.fs_name">
         </cd-cephfs-subvolume-snapshots-list>
       </ng-template>
     </ng-container>
+    <ng-container ngbNavItem="snapshot-schedules">
+      <a
+      ngbNavLink
+      i18n>Snapshot schedules</a>
+      <ng-template ngbNavContent>
+        <cd-cephfs-snapshotschedule-list
+          [fsName]="selection.mdsmap.fs_name"
+          [id]="id"
+        ></cd-cephfs-snapshotschedule-list>
+      </ng-template>
+    </ng-container>
     <ng-container ngbNavItem="clients">
       <a ngbNavLink>
         <ng-container i18n>Clients</ng-container>
         <span class="badge badge-pill badge-tab ms-1">{{ clients.data.length }}</span>
       </a>
       <ng-template ngbNavContent>
-        <cd-cephfs-clients [id]="id"
-                           [clients]="clients"
-                           (triggerApiUpdate)="refresh()">
+        <cd-cephfs-clients
+        [id]="id"
+        [clients]="clients"
+        (triggerApiUpdate)="refresh()">
         </cd-cephfs-clients>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="performance-details">
-      <a ngbNavLink
-         i18n>Performance Details</a>
+      <a
+      ngbNavLink
+      i18n>Performance Details</a>
       <ng-template ngbNavContent>
-        <cd-grafana i18n-title
-                    title="CephFS MDS performance"
-                    [grafanaPath]="'mds-performance?var-mds_servers=mds.' + grafanaId"
-                    [type]="'metrics'"
-                    uid="tbO9LAiZz"
-                    grafanaStyle="one">
+        <cd-grafana
+          i18n-title
+          title="CephFS MDS performance"
+          [grafanaPath]="'mds-performance?var-mds_servers=mds.' + grafanaId"
+          [type]="'metrics'"
+          uid="tbO9LAiZz"
+          grafanaStyle="one"
+        >
         </cd-grafana>
       </ng-template>
     </ng-container>
index 621a67d03c8556d0fe0eed4a635f2424b78ad0b6..c971c9d85ad49bcebf0f57abcc6cf1953c93c34b 100644 (file)
@@ -21,6 +21,8 @@ import { CephfsSubvolumeGroupComponent } from './cephfs-subvolume-group/cephfs-s
 import { CephfsSubvolumegroupFormComponent } from './cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component';
 import { CephfsSubvolumeSnapshotsListComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component';
 import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component';
+import { CephfsSnapshotscheduleListComponent } from './cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component';
+import { DataTableModule } from '../../shared/datatable/datatable.module';
 
 @NgModule({
   imports: [
@@ -33,7 +35,8 @@ import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapsh
     FormsModule,
     ReactiveFormsModule,
     NgbTypeaheadModule,
-    NgbTooltipModule
+    NgbTooltipModule,
+    DataTableModule
   ],
   declarations: [
     CephfsDetailComponent,
@@ -49,7 +52,8 @@ import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapsh
     CephfsSubvolumeGroupComponent,
     CephfsSubvolumegroupFormComponent,
     CephfsSubvolumeSnapshotsListComponent,
-    CephfsSubvolumeSnapshotsFormComponent
+    CephfsSubvolumeSnapshotsFormComponent,
+    CephfsSnapshotscheduleListComponent
   ]
 })
 export class CephfsModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.spec.ts
new file mode 100644 (file)
index 0000000..766b8f3
--- /dev/null
@@ -0,0 +1,22 @@
+import { TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { CephfsSnapshotScheduleService } from './cephfs-snapshot-schedule.service';
+
+describe('CephfsSnapshotScheduleService', () => {
+  let service: CephfsSnapshotScheduleService;
+
+  configureTestBed({
+    providers: [CephfsSnapshotScheduleService],
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    service = TestBed.inject(CephfsSnapshotScheduleService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts
new file mode 100644 (file)
index 0000000..ec9f58c
--- /dev/null
@@ -0,0 +1,39 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { SnapshotSchedule } from '../models/snapshot-schedule';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class CephfsSnapshotScheduleService {
+  baseURL = 'api/cephfs';
+
+  constructor(private http: HttpClient) {}
+
+  getSnapshotScheduleList(
+    path: string,
+    fs: string,
+    recursive = true
+  ): Observable<SnapshotSchedule[]> {
+    return this.http
+      .get<SnapshotSchedule[]>(
+        `${this.baseURL}/snaphost/schedule?path=${path}&fs=${fs}&recursive=${recursive}`
+      )
+      .pipe(
+        map((snapList: SnapshotSchedule[]) =>
+          snapList.map((snapItem: SnapshotSchedule) => ({
+            ...snapItem,
+            status: snapItem.active ? 'Active' : 'Inactive',
+            subvol: snapItem?.subvol || ' - ',
+            retention: Object.values(snapItem.retention)?.length
+              ? Object.entries(snapItem.retention)
+                  ?.map?.(([frequency, interval]) => `${interval}${frequency.toLocaleUpperCase()}`)
+                  .join(' ')
+              : '-'
+          }))
+        )
+      );
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/snapshot-schedule.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/snapshot-schedule.ts
new file mode 100644 (file)
index 0000000..b1cea74
--- /dev/null
@@ -0,0 +1,17 @@
+export interface SnapshotSchedule {
+  fs?: string;
+  subvol?: string;
+  path: string;
+  rel_path?: string;
+  schedule: string;
+  retention?: Record<string, number> | string;
+  start: Date;
+  created: Date;
+  first?: string;
+  last?: string;
+  last_pruned?: string;
+  created_count?: number;
+  pruned_count?: number;
+  active: boolean;
+  status: 'Active' | 'Inactive';
+}
index 7d7508f5d2b7871e51a3c67b167e3ac85d991ba7..da0ea9465be93e9efc0ac7075a367f442c976bf7 100644 (file)
@@ -1735,6 +1735,43 @@ paths:
       summary: Rename CephFS Volume
       tags:
       - Cephfs
+  /api/cephfs/snaphost/schedule:
+    get:
+      parameters:
+      - in: query
+        name: fs
+        required: true
+        schema:
+          type: string
+      - default: /
+        in: query
+        name: path
+        schema:
+          type: string
+      - default: true
+        in: query
+        name: recursive
+        schema:
+          type: boolean
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          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: []
+      tags:
+      - CephFSSnapshotSchedule
   /api/cephfs/subvolume:
     post:
       parameters: []
@@ -12881,6 +12918,8 @@ servers:
 tags:
 - description: Initiate a session with Ceph
   name: Auth
+- description: Cephfs Snapshot Scheduling API
+  name: CephFSSnapshotSchedule
 - description: CephFS Subvolume Management API
   name: CephFSSubvolume
 - description: Cephfs Management API