From 50229fbd288bbfd3052199b7314699ffb181b0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr> Date: Tue, 22 Mar 2022 22:13:24 +0100 Subject: [PATCH] #27 => WIP --- .../app/admin/instance/components/index.ts | 6 +- .../components/instance-form.component.html | 8 + .../components/instance-form.component.ts | 1 + .../instance-group-form.component.html | 29 ++ .../instance-group-form.component.ts | 78 ++++ .../instance-group-table.component.html | 37 ++ .../instance-group-table.component.ts | 22 + .../edit-instance-group.component.html | 33 ++ .../edit-instance-group.component.ts | 47 ++ .../instance-group-list.component.html | 33 ++ .../instance-group-list.component.ts | 40 ++ .../containers/instance-list.component.html | 8 + .../new-instance-group.component.html | 33 ++ .../new-instance-group.component.ts | 36 ++ .../admin/instance/instance-routing.module.ts | 11 +- .../actions/instance-group.actions.ts | 25 + client/src/app/metamodel/effects/index.ts | 2 + .../effects/instance-group.effects.ts | 159 +++++++ client/src/app/metamodel/metamodel.reducer.ts | 3 + client/src/app/metamodel/models/index.ts | 1 + .../metamodel/models/instance-group.model.ts | 19 + .../reducers/instance-group.reducer.ts | 82 ++++ .../selectors/instance-group.selector.ts | 54 +++ client/src/app/metamodel/services/index.ts | 2 + .../services/instance-group.service.ts | 67 +++ conf-dev/create-db.sh | 2 +- server/app/dependencies.php | 10 +- server/app/routes.php | 2 + .../doctrine-proxy/__CG__AppEntityDataset.php | 430 +++++++++++++++++- .../__CG__AppEntityInstance.php | 48 +- .../__CG__AppEntityInstanceGroup.php | 250 ++++++++++ server/src/Action/InstanceAction.php | 1 + server/src/Action/InstanceGroupAction.php | 125 +++++ server/src/Action/InstanceGroupListAction.php | 113 +++++ server/src/Action/InstanceListAction.php | 1 + server/src/Entity/Instance.php | 20 +- server/src/Entity/InstanceGroup.php | 92 ++++ 37 files changed, 1906 insertions(+), 24 deletions(-) create mode 100644 client/src/app/admin/instance/components/instance-group-form.component.html create mode 100644 client/src/app/admin/instance/components/instance-group-form.component.ts create mode 100644 client/src/app/admin/instance/components/instance-group-table.component.html create mode 100644 client/src/app/admin/instance/components/instance-group-table.component.ts create mode 100644 client/src/app/admin/instance/containers/edit-instance-group.component.html create mode 100644 client/src/app/admin/instance/containers/edit-instance-group.component.ts create mode 100644 client/src/app/admin/instance/containers/instance-group-list.component.html create mode 100644 client/src/app/admin/instance/containers/instance-group-list.component.ts create mode 100644 client/src/app/admin/instance/containers/new-instance-group.component.html create mode 100644 client/src/app/admin/instance/containers/new-instance-group.component.ts create mode 100644 client/src/app/metamodel/actions/instance-group.actions.ts create mode 100644 client/src/app/metamodel/effects/instance-group.effects.ts create mode 100644 client/src/app/metamodel/models/instance-group.model.ts create mode 100644 client/src/app/metamodel/reducers/instance-group.reducer.ts create mode 100644 client/src/app/metamodel/selectors/instance-group.selector.ts create mode 100644 client/src/app/metamodel/services/instance-group.service.ts create mode 100644 server/doctrine-proxy/__CG__AppEntityInstanceGroup.php create mode 100644 server/src/Action/InstanceGroupAction.php create mode 100644 server/src/Action/InstanceGroupListAction.php create mode 100644 server/src/Entity/InstanceGroup.php diff --git a/client/src/app/admin/instance/components/index.ts b/client/src/app/admin/instance/components/index.ts index dda1856a..1927331f 100644 --- a/client/src/app/admin/instance/components/index.ts +++ b/client/src/app/admin/instance/components/index.ts @@ -9,8 +9,12 @@ import { InstanceCardComponent } from './instance-card.component'; import { InstanceFormComponent } from './instance-form.component'; +import { InstanceGroupTableComponent } from './instance-group-table.component'; +import { InstanceGroupFormComponent } from './instance-group-form.component'; export const dummiesComponents = [ InstanceCardComponent, - InstanceFormComponent + InstanceFormComponent, + InstanceGroupTableComponent, + InstanceGroupFormComponent ]; diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html index 9243f79a..e756ad55 100644 --- a/client/src/app/admin/instance/components/instance-form.component.html +++ b/client/src/app/admin/instance/components/instance-form.component.html @@ -24,6 +24,14 @@ [rootDirectoryIsLoaded]="rootDirectoryIsLoaded" (loadRootDirectory)="loadRootDirectory.emit($event)"> </app-data-path-form-control> + <div class="custom-control custom-radio custom-control-inline"> + <input type="radio" class="custom-control-input" id="public" formControlName="public" [value]="true"> + <label class="custom-control-label" for="public"><span class="fas fa-globe"></span> Public</label> + </div> + <div class="custom-control custom-radio custom-control-inline"> + <input type="radio" class="custom-control-input" id="private" formControlName="public" [value]="false"> + <label class="custom-control-label" for="private"><span class="fas fa-lock"></span> Private</label> + </div> </accordion-group> <accordion-group heading="Design" [isOpen]="true"> <app-file-select-form-control diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts index 7166cfe4..407768ea 100644 --- a/client/src/app/admin/instance/components/instance-form.component.ts +++ b/client/src/app/admin/instance/components/instance-form.component.ts @@ -30,6 +30,7 @@ export class InstanceFormComponent implements OnInit { description: new FormControl('', [Validators.required]), display: new FormControl('', [Validators.required]), data_path: new FormControl(''), + public: new FormControl(true, [Validators.required]), portal_logo: new FormControl(''), design_color: new FormControl('#7AC29A', [Validators.required]), design_background_color: new FormControl('#7AC29A', [Validators.required]), diff --git a/client/src/app/admin/instance/components/instance-group-form.component.html b/client/src/app/admin/instance/components/instance-group-form.component.html new file mode 100644 index 00000000..713ae9b9 --- /dev/null +++ b/client/src/app/admin/instance/components/instance-group-form.component.html @@ -0,0 +1,29 @@ +<form [formGroup]="form" (ngSubmit)="submit()" novalidate> + <div class="form-group"> + <label for="role">Role</label> + <input type="text" class="form-control" id="role" name="role" formControlName="role"> + </div> + <div class="form-group"> + <div class="form-row h-100"> + <div class="col-4"> + <label for="instances">Available instances</label> + <select multiple class="form-control" name="availableInstances" #selectAvailableInstances> + <option *ngFor="let instance of getAvailableInstances()" [value]="instance.name">{{instance.name}}</option> + </select> + </div> + <div class="col-2 text-center my-auto"> + <button (click)="addInstances(selectAvailableInstances)" type="button" class="btn btn-dark mb-1">>>></button><br> + <button (click)="removeInstances(selectGroupInstances)" type="button" class="btn btn-dark"><<<</button> + </div> + <div class="col-4"> + <label for="instance">Group's instance</label> + <select multiple class="form-control" #selectGroupInstances> + <option *ngFor="let instance of instanceGroupInstances" [value]="instance">{{instance}}</option> + </select> + </div> + </div> + </div> + <div class="form-group"> + <ng-content></ng-content> + </div> +</form> diff --git a/client/src/app/admin/instance/components/instance-group-form.component.ts b/client/src/app/admin/instance/components/instance-group-form.component.ts new file mode 100644 index 00000000..a7dcd74e --- /dev/null +++ b/client/src/app/admin/instance/components/instance-group-form.component.ts @@ -0,0 +1,78 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; + +import { InstanceGroup, Instance } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-instance-group-form', + templateUrl: 'instance-group-form.component.html' +}) +export class InstanceGroupFormComponent implements OnInit { + @Input() instanceGroup: InstanceGroup; + @Input() instanceList: Instance[]; + @Output() onSubmit: EventEmitter<InstanceGroup> = new EventEmitter(); + + public instanceGroupInstances = []; + public form = new FormGroup({ + role: new FormControl('', [Validators.required]) + }); + + ngOnInit() { + if (this.instanceGroup) { + this.form.patchValue(this.instanceGroup); + this.instanceGroupInstances = [...this.instanceGroup.instances]; + } + } + + submit() { + if (this.instanceGroup) { + this.onSubmit.emit({ + ...this.instanceGroup, + ...this.form.value, + instances: this.instanceGroupInstances + }); + } else { + this.onSubmit.emit({ + ...this.form.value, + instances: this.instanceGroupInstances + }); + } + } + + getAvailableInstances(): Instance[] { + return this.instanceList.filter(instance => !this.instanceGroupInstances.includes(instance.name)); + } + + addInstances(selectElement) { + let availableInstanceSelected = []; + for (var i = 0; i < selectElement.options.length; i++) { + const optionElement = selectElement.options[i]; + if (optionElement.selected == true) { + availableInstanceSelected.push(optionElement.value); + } + } + this.instanceGroupInstances.push(...availableInstanceSelected); + this.form.markAsDirty(); + } + + removeInstances(selectElement) { + let instanceGroupInstancesSelected = []; + for (var i = 0; i < selectElement.options.length; i++) { + const optionElement = selectElement.options[i]; + if (optionElement.selected == true) { + instanceGroupInstancesSelected.push(optionElement.value); + } + } + this.instanceGroupInstances = [...this.instanceGroupInstances.filter(instance => !instanceGroupInstancesSelected.includes(instance))] + this.form.markAsDirty(); + } +} diff --git a/client/src/app/admin/instance/components/instance-group-table.component.html b/client/src/app/admin/instance/components/instance-group-table.component.html new file mode 100644 index 00000000..ab148aca --- /dev/null +++ b/client/src/app/admin/instance/components/instance-group-table.component.html @@ -0,0 +1,37 @@ +<div class="table-responsive"> + <table class="table table-striped" aria-describedby="Group list"> + <thead> + <tr> + <th scope="col">ID</th> + <th scope="col">Role</th> + <th scope="col">Instances</th> + <th scope="col">Actions</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let instanceGroup of instanceGroupList"> + <td class="align-middle">{{ instanceGroup.id }}</td> + <td class="align-middle">{{ instanceGroup.role }}</td> + <td class="align-middle"> + <span *ngIf="instanceGroup.instances.length < 1" class="badge badge-pill badge-warning"> + Empty + </span> + <span *ngFor="let instance of instanceGroup.instances" class="badge badge-pill badge-info mr-1"> + {{ instance }} + </span> + </td> + <td class="align-middle"> + <a title="Edit this group" routerLink="edit-group/{{instanceGroup.id}}" class="btn btn-outline-primary"> + <span class="fas fa-edit"></span> + </a> + + <app-delete-btn + [type]="'instanceGroup'" + [label]="instanceGroup.role" + (confirm)="deleteInstanceGroup.emit(instanceGroup)"> + </app-delete-btn> + </td> + </tr> + </tbody> + </table> +</div> diff --git a/client/src/app/admin/instance/components/instance-group-table.component.ts b/client/src/app/admin/instance/components/instance-group-table.component.ts new file mode 100644 index 00000000..e4982f0e --- /dev/null +++ b/client/src/app/admin/instance/components/instance-group-table.component.ts @@ -0,0 +1,22 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core'; + +import { InstanceGroup } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-instance-group-table', + templateUrl: 'instance-group-table.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InstanceGroupTableComponent { + @Input() instanceGroupList: InstanceGroup[]; + @Output() deleteInstanceGroup: EventEmitter<InstanceGroup> = new EventEmitter(); +} diff --git a/client/src/app/admin/instance/containers/edit-instance-group.component.html b/client/src/app/admin/instance/containers/edit-instance-group.component.html new file mode 100644 index 00000000..99eb49d8 --- /dev/null +++ b/client/src/app/admin/instance/containers/edit-instance-group.component.html @@ -0,0 +1,33 @@ +<div class="container-fluid"> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a routerLink="/admin/instance/instance-list"> + Instances + </a> + </li> + <li class="breadcrumb-item" aria-current="page"> + <a routerLink="/admin/instance/group"> + Instance groups + </a> + </li> + <li *ngIf="instanceGroupListIsLoaded | async" class="breadcrumb-item active" aria-current="page">Edit instance group {{ (instanceGroup | async).role }}</li> + </ol> + </nav> +</div> + +<div class="container"> + <app-spinner *ngIf="(instanceGroupListIsLoading | async) || (instanceListIsLoading | async)"></app-spinner> + + <div *ngIf="(instanceGroupListIsLoaded | async) && (instanceListIsLoaded | async)" class="row"> + <div class="col-12"> + <app-instance-group-form [instanceGroup]="instanceGroup | async" [instanceList]="instanceList | async" (onSubmit)="editInstanceGroup($event)" #formGroup> + <button [disabled]="!formGroup.form.valid || formGroup.form.pristine" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Update instance group information + </button> + + <a routerLink="/admin/instance/group" role="button" class="btn btn-danger">Cancel</a> + </app-instance-group-form> + </div> + </div> +</div> diff --git a/client/src/app/admin/instance/containers/edit-instance-group.component.ts b/client/src/app/admin/instance/containers/edit-instance-group.component.ts new file mode 100644 index 00000000..47dd9caf --- /dev/null +++ b/client/src/app/admin/instance/containers/edit-instance-group.component.ts @@ -0,0 +1,47 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; + +import { InstanceGroup, Instance } from 'src/app/metamodel/models'; +import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; +import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions'; +import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector'; + +@Component({ + selector: 'app-edit-instance-group', + templateUrl: 'edit-instance-group.component.html' +}) +export class EditInstanceGroupComponent implements OnInit { + public instanceGroupListIsLoading: Observable<boolean>; + public instanceGroupListIsLoaded: Observable<boolean>; + public instanceGroup: Observable<InstanceGroup>; + public instanceListIsLoading: Observable<boolean>; + public instanceListIsLoaded: Observable<boolean>; + public instanceList: Observable<Instance[]>; + + constructor(private store: Store<{ }>) { + this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading); + this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded); + this.instanceGroup = store.select(instanceGroupSelector.selectInstanceGroupByRouteId); + this.instanceListIsLoading = store.select(instanceSelector.selectInstanceListIsLoading); + this.instanceListIsLoaded = store.select(instanceSelector.selectInstanceListIsLoaded); + this.instanceList = store.select(instanceSelector.selectAllInstances); + } + + ngOnInit() { + Promise.resolve(null).then(() => this.store.dispatch(instanceGroupActions.loadInstanceGroupList())); + } + + editInstanceGroup(instanceGroup: InstanceGroup) { + this.store.dispatch(instanceGroupActions.editInstanceGroup({ instanceGroup })); + } +} diff --git a/client/src/app/admin/instance/containers/instance-group-list.component.html b/client/src/app/admin/instance/containers/instance-group-list.component.html new file mode 100644 index 00000000..62a39da7 --- /dev/null +++ b/client/src/app/admin/instance/containers/instance-group-list.component.html @@ -0,0 +1,33 @@ +<div class="container-fluid"> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a routerLink="/admin/instance/instance-list"> + Instances + </a> + </li> + <li class="breadcrumb-item active" aria-current="page">Handle instance groups</li> + </ol> + </nav> + + <app-spinner *ngIf="instanceGroupListIsLoading | async"></app-spinner> + + <ng-container *ngIf="instanceGroupListIsLoaded | async"> + <div class="row"> + <div class="col-12"> + <button title="Add a new instance group" class="btn btn-outline-success float-right" routerLink="new-group"> + <span class="fas fa-plus"></span> New instance group + </button> + </div> + </div> + + <div class="row mt-1"> + <div class="col-12"> + <app-instance-group-table + [instanceGroupList]="instanceGroupList | async" + (deleteInstanceGroup)="deleteInstanceGroup($event)"> + </app-instance-group-table> + </div> + </div> + </ng-container> +</div> diff --git a/client/src/app/admin/instance/containers/instance-group-list.component.ts b/client/src/app/admin/instance/containers/instance-group-list.component.ts new file mode 100644 index 00000000..a3f054a8 --- /dev/null +++ b/client/src/app/admin/instance/containers/instance-group-list.component.ts @@ -0,0 +1,40 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; + +import { InstanceGroup } from 'src/app/metamodel/models'; +import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions'; +import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector'; + +@Component({ + selector: 'app-instance-group-list', + templateUrl: 'instance-group-list.component.html' +}) +export class InstanceGroupListComponent implements OnInit { + public instanceGroupListIsLoading: Observable<boolean>; + public instanceGroupListIsLoaded: Observable<boolean>; + public instanceGroupList: Observable<InstanceGroup[]>; + + constructor(private store: Store<{ }>) { + this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading); + this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded); + this.instanceGroupList = store.select(instanceGroupSelector.selectAllInstanceGroups); + } + + ngOnInit() { + Promise.resolve(null).then(() => this.store.dispatch(instanceGroupActions.loadInstanceGroupList())); + } + + deleteInstanceGroup(instanceGroup: InstanceGroup) { + this.store.dispatch(instanceGroupActions.deleteInstanceGroup({ instanceGroup })); + } +} diff --git a/client/src/app/admin/instance/containers/instance-list.component.html b/client/src/app/admin/instance/containers/instance-list.component.html index ce9430bf..cc56107c 100644 --- a/client/src/app/admin/instance/containers/instance-list.component.html +++ b/client/src/app/admin/instance/containers/instance-list.component.html @@ -7,6 +7,14 @@ </div> <div class="container"> + <div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups"> + <div class="btn-group mr-2" role="group" aria-label="Second group"> + <button routerLink="/admin/instance/instance-group" title="Handle instance groups" class="btn btn-outline-primary"> + <span class="fas fa-users"></span> Handle instance groups + </button> + </div> + </div> + <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3"> <div class="col mb-3" *ngFor="let instance of (instanceList | async)"> <app-instance-card diff --git a/client/src/app/admin/instance/containers/new-instance-group.component.html b/client/src/app/admin/instance/containers/new-instance-group.component.html new file mode 100644 index 00000000..e6af4e6c --- /dev/null +++ b/client/src/app/admin/instance/containers/new-instance-group.component.html @@ -0,0 +1,33 @@ +<div class="container-fluid"> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a routerLink="/admin/instance/instance-list"> + Instances + </a> + </li> + <li class="breadcrumb-item" aria-current="page"> + <a routerLink="/admin/instance/group"> + Instance groups + </a> + </li> + <li class="breadcrumb-item active" aria-current="page">Add a new instance group</li> + </ol> + </nav> +</div> + +<div class="container"> + <app-spinner *ngIf="instanceListIsLoading | async"></app-spinner> + + <div *ngIf="instanceListIsLoaded | async" class="row"> + <div class="col-12"> + <app-instance-group-form [instanceList]="instanceList | async" (onSubmit)="addNewInstanceGroup($event)" #formGroup> + <button [disabled]="!formGroup.form.valid || formGroup.form.pristine" type="submit" class="btn btn-primary"> + <span class="fa fa-database"></span> Add the new instance group + </button> + + <a routerLink="/admin/instance/instance-group" type="button" class="btn btn-danger">Cancel</a> + </app-instance-group-form> + </div> + </div> +</div> diff --git a/client/src/app/admin/instance/containers/new-instance-group.component.ts b/client/src/app/admin/instance/containers/new-instance-group.component.ts new file mode 100644 index 00000000..48d2bc2f --- /dev/null +++ b/client/src/app/admin/instance/containers/new-instance-group.component.ts @@ -0,0 +1,36 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; + +import { InstanceGroup, Instance } from 'src/app/metamodel/models'; +import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions'; +import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; + +@Component({ + selector: 'app-new-instance-group', + templateUrl: 'new-instance-group.component.html' +}) +export class NewInstanceGroupComponent { + public instanceListIsLoading: Observable<boolean>; + public instanceListIsLoaded: Observable<boolean>; + public instanceList: Observable<Instance[]>; + + constructor(private store: Store<{ }>) { + this.instanceListIsLoading = store.select(instanceSelector.selectInstanceListIsLoading); + this.instanceListIsLoaded = store.select(instanceSelector.selectInstanceListIsLoaded); + this.instanceList = store.select(instanceSelector.selectAllInstances); + } + + addNewInstanceGroup(instanceGroup: InstanceGroup) { + this.store.dispatch(instanceGroupActions.addInstanceGroup({ instanceGroup })); + } +} diff --git a/client/src/app/admin/instance/instance-routing.module.ts b/client/src/app/admin/instance/instance-routing.module.ts index 7c160511..4e2503ca 100644 --- a/client/src/app/admin/instance/instance-routing.module.ts +++ b/client/src/app/admin/instance/instance-routing.module.ts @@ -14,11 +14,17 @@ import { InstanceListComponent } from './containers/instance-list.component'; import { NewInstanceComponent } from './containers/new-instance.component'; import { EditInstanceComponent } from './containers/edit-instance.component'; import { ConfigureInstanceComponent } from './containers/configure-instance.component'; +import { InstanceGroupListComponent } from './containers/instance-group-list.component'; +import { NewInstanceGroupComponent } from './containers/new-instance-group.component'; +import { EditInstanceGroupComponent } from './containers/edit-instance-group.component'; const routes: Routes = [ { path: 'instance-list', component: InstanceListComponent }, { path: 'new-instance', component: NewInstanceComponent }, { path: 'edit-instance/:iname', component: EditInstanceComponent }, + { path: 'instance-group', component: InstanceGroupListComponent }, + { path: 'instance-group/new-group', component: NewInstanceGroupComponent }, + { path: 'instance-group/edit-group/:id', component: EditInstanceGroupComponent }, { path: 'configure-instance/:iname', component: ConfigureInstanceComponent, children: [ { path: '', redirectTo: 'dataset/dataset-list', pathMatch: 'full' }, @@ -42,5 +48,8 @@ export const routedComponents = [ InstanceListComponent, NewInstanceComponent, EditInstanceComponent, - ConfigureInstanceComponent + ConfigureInstanceComponent, + InstanceGroupListComponent, + NewInstanceGroupComponent, + EditInstanceGroupComponent ]; diff --git a/client/src/app/metamodel/actions/instance-group.actions.ts b/client/src/app/metamodel/actions/instance-group.actions.ts new file mode 100644 index 00000000..f5f97239 --- /dev/null +++ b/client/src/app/metamodel/actions/instance-group.actions.ts @@ -0,0 +1,25 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createAction, props } from '@ngrx/store'; + +import { InstanceGroup } from '../models'; + +export const loadInstanceGroupList = createAction('[Metamodel] Load Instance Group List'); +export const loadInstanceGroupListSuccess = createAction('[Metamodel] Load Instance Group List Success', props<{ instanceGroups: InstanceGroup[] }>()); +export const loadInstanceGroupListFail = createAction('[Metamodel] Load Instance Group List Fail'); +export const addInstanceGroup = createAction('[Metamodel] Add Instance Group', props<{ instanceGroup: InstanceGroup }>()); +export const addInstanceGroupSuccess = createAction('[Metamodel] Add Instance Group Success', props<{ instanceGroup: InstanceGroup }>()); +export const addInstanceGroupFail = createAction('[Metamodel] Add Instance Group Fail'); +export const editInstanceGroup = createAction('[Metamodel] Edit Instance Group', props<{ instanceGroup: InstanceGroup }>()); +export const editInstanceGroupSuccess = createAction('[Metamodel] Edit Instance Group Success', props<{ instanceGroup: InstanceGroup }>()); +export const editInstanceGroupFail = createAction('[Metamodel] Edit Instance Group Fail'); +export const deleteInstanceGroup = createAction('[Metamodel] Delete Instance Group', props<{ instanceGroup: InstanceGroup }>()); +export const deleteInstanceGroupSuccess = createAction('[Metamodel] Delete Instance Group Success', props<{ instanceGroup: InstanceGroup }>()); +export const deleteInstanceGroupFail = createAction('[Metamodel] Delete Instance Group Fail'); diff --git a/client/src/app/metamodel/effects/index.ts b/client/src/app/metamodel/effects/index.ts index 311be4c1..2be316f4 100644 --- a/client/src/app/metamodel/effects/index.ts +++ b/client/src/app/metamodel/effects/index.ts @@ -16,6 +16,7 @@ import { DatasetFamilyEffects } from './dataset-family.effects'; import { DatasetEffects } from './dataset.effects'; import { AttributeEffects } from './attribute.effects'; import { GroupEffects } from './group.effects'; +import { InstanceGroupEffects } from './instance-group.effects'; import { CriteriaFamilyEffects } from './criteria-family.effects'; import { OutputCategoryEffects } from './output-category.effects'; import { OutputFamilyEffects } from './output-family.effects'; @@ -34,6 +35,7 @@ export const metamodelEffects = [ DatasetEffects, AttributeEffects, GroupEffects, + InstanceGroupEffects, CriteriaFamilyEffects, OutputCategoryEffects, OutputFamilyEffects, diff --git a/client/src/app/metamodel/effects/instance-group.effects.ts b/client/src/app/metamodel/effects/instance-group.effects.ts new file mode 100644 index 00000000..97892873 --- /dev/null +++ b/client/src/app/metamodel/effects/instance-group.effects.ts @@ -0,0 +1,159 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { map, tap, mergeMap, catchError } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; + +import * as instanceGroupActions from '../actions/instance-group.actions'; +import { InstanceGroupService } from '../services/instance-group.service'; + +/** + * @class + * @classdesc Survey effects. + */ +@Injectable() +export class InstanceGroupEffects { + /** + * Calls action to retrieve instance group list. + */ + loadInstanceGroups$ = createEffect((): any => + this.actions$.pipe( + ofType(instanceGroupActions.loadInstanceGroupList), + mergeMap(() => this.instanceGroupService.retrieveInstanceGroupList() + .pipe( + map(instanceGroups => instanceGroupActions.loadInstanceGroupListSuccess({ instanceGroups })), + catchError(() => of(instanceGroupActions.loadInstanceGroupListFail())) + ) + ) + ) + ); + + /** + * Calls action to add a instanceGroup. + */ + addInstanceGroup$ = createEffect((): any => + this.actions$.pipe( + ofType(instanceGroupActions.addInstanceGroup), + mergeMap(action => this.instanceGroupService.addInstanceGroup(action.instanceGroup) + .pipe( + map(instanceGroup => instanceGroupActions.addInstanceGroupSuccess({ instanceGroup })), + catchError(() => of(instanceGroupActions.addInstanceGroupFail())) + ) + ) + ) + ); + + /** + * Displays add instanceGroup success notification. + */ + addInstanceGroupSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.addInstanceGroupSuccess), + tap(() => { + this.router.navigateByUrl(`/admin/instance/instance-group`); + this.toastr.success('Instance group successfully added', 'The new instance group was added into the database') + }) + ), { dispatch: false } + ); + + /** + * Displays add instanceGroup error notification. + */ + addInstanceGroupFail$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.addInstanceGroupFail), + tap(() => this.toastr.error('Failure to add instance group', 'The new instance group could not be added into the database')) + ), { dispatch: false } + ); + + /** + * Calls action to modify a instanceGroup. + */ + editInstanceGroup$ = createEffect((): any => + this.actions$.pipe( + ofType(instanceGroupActions.editInstanceGroup), + mergeMap(action => this.instanceGroupService.editInstanceGroup(action.instanceGroup) + .pipe( + map(instanceGroup => instanceGroupActions.editInstanceGroupSuccess({ instanceGroup })), + catchError(() => of(instanceGroupActions.editInstanceGroupFail())) + ) + ) + ) + ); + + /** + * Displays edit instanceGroup success notification. + */ + editInstanceGroupSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.editInstanceGroupSuccess), + tap(() => { + this.router.navigateByUrl(`/admin/instance/instance-group`); + this.toastr.success('Instance group successfully edited', 'The existing instance group has been edited into the database') + }) + ), { dispatch: false } + ); + + /** + * Displays edit instanceGroup error notification. + */ + editInstanceGroupFail$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.editInstanceGroupFail), + tap(() => this.toastr.error('Failure to edit instance group', 'The existing instance group could not be edited into the database')) + ), { dispatch: false } + ); + + /** + * Calls action to remove a instanceGroup. + */ + deleteInstanceGroup$ = createEffect((): any => + this.actions$.pipe( + ofType(instanceGroupActions.deleteInstanceGroup), + mergeMap(action => this.instanceGroupService.deleteInstanceGroup(action.instanceGroup.id) + .pipe( + map(() => instanceGroupActions.deleteInstanceGroupSuccess({ instanceGroup: action.instanceGroup })), + catchError(() => of(instanceGroupActions.deleteInstanceGroupFail())) + ) + ) + ) + ); + + /** + * Displays delete instanceGroup success notification. + */ + deleteInstanceGroupSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.deleteInstanceGroupSuccess), + tap(() => this.toastr.success('Instance group successfully deleted', 'The existing instance group has been deleted')) + ), { dispatch: false } + ); + + /** + * Displays delete instanceGroup error notification. + */ + deleteInstanceGroupFail$ = createEffect(() => + this.actions$.pipe( + ofType(instanceGroupActions.deleteInstanceGroupFail), + tap(() => this.toastr.error('Failure to delete instance group', 'The existing instance group could not be deleted from the database')) + ), { dispatch: false } + ); + + constructor( + private actions$: Actions, + private instanceGroupService: InstanceGroupService, + private router: Router, + private toastr: ToastrService + ) {} +} diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts index d7da5059..2873d835 100644 --- a/client/src/app/metamodel/metamodel.reducer.ts +++ b/client/src/app/metamodel/metamodel.reducer.ts @@ -15,6 +15,7 @@ import * as table from './reducers/table.reducer'; import * as column from './reducers/column.reducer'; import * as survey from './reducers/survey.reducer'; import * as group from './reducers/group.reducer'; +import * as instanceGroup from './reducers/instance-group.reducer'; import * as dataset from './reducers/dataset.reducer'; import * as datasetFamily from './reducers/dataset-family.reducer'; import * as instance from './reducers/instance.reducer'; @@ -38,6 +39,7 @@ export interface State { column: column.State; survey: survey.State; group: group.State; + instanceGroup: instanceGroup.State; dataset: dataset.State; datasetFamily: datasetFamily.State; instance: instance.State; @@ -57,6 +59,7 @@ const reducers = { column: column.columnReducer, survey: survey.surveyReducer, group: group.groupReducer, + instanceGroup: instanceGroup.instanceGroupReducer, dataset: dataset.datasetReducer, datasetFamily: datasetFamily.datasetFamilyReducer, instance: instance.instanceReducer, diff --git a/client/src/app/metamodel/models/index.ts b/client/src/app/metamodel/models/index.ts index 4bbbbc18..4144765a 100644 --- a/client/src/app/metamodel/models/index.ts +++ b/client/src/app/metamodel/models/index.ts @@ -10,6 +10,7 @@ export * from './database.model'; export * from './survey.model'; export * from './group.model'; +export * from './instance-group.model'; export * from './dataset.model'; export * from './dataset-family.model'; export * from './instance.model'; diff --git a/client/src/app/metamodel/models/instance-group.model.ts b/client/src/app/metamodel/models/instance-group.model.ts new file mode 100644 index 00000000..48b3a147 --- /dev/null +++ b/client/src/app/metamodel/models/instance-group.model.ts @@ -0,0 +1,19 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for group. + * + * @interface Group + */ +export interface InstanceGroup { + id: number; + role: string; + instances: string[]; +} diff --git a/client/src/app/metamodel/reducers/instance-group.reducer.ts b/client/src/app/metamodel/reducers/instance-group.reducer.ts new file mode 100644 index 00000000..cef704ca --- /dev/null +++ b/client/src/app/metamodel/reducers/instance-group.reducer.ts @@ -0,0 +1,82 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createReducer, on } from '@ngrx/store'; +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; + +import { InstanceGroup } from '../models'; +import * as instanceGroupActions from '../actions/instance-group.actions'; + +/** + * Interface for instanceGroup state. + * + * @interface State + */ +export interface State extends EntityState<InstanceGroup> { + instanceGroupListIsLoading: boolean; + instanceGroupListIsLoaded: boolean; +} + +export const adapter: EntityAdapter<InstanceGroup> = createEntityAdapter<InstanceGroup>(); + +export const initialState: State = adapter.getInitialState({ + instanceGroupListIsLoading: false, + instanceGroupListIsLoaded: false +}); + +export const instanceGroupReducer = createReducer( + initialState, + on(instanceGroupActions.loadInstanceGroupList, (state) => { + return { + ...state, + instanceGroupListIsLoading: true + } + }), + on(instanceGroupActions.loadInstanceGroupListSuccess, (state, { instanceGroups }) => { + return adapter.setAll( + instanceGroups, + { + ...state, + instanceGroupListIsLoading: false, + instanceGroupListIsLoaded: true + } + ); + }), + on(instanceGroupActions.loadInstanceGroupListFail, (state) => { + return { + ...state, + instanceGroupListIsLoading: false + } + }), + on(instanceGroupActions.addInstanceGroupSuccess, (state, { instanceGroup }) => { + return adapter.addOne(instanceGroup, state) + }), + on(instanceGroupActions.editInstanceGroupSuccess, (state, { instanceGroup }) => { + return adapter.setOne(instanceGroup, state) + }), + on(instanceGroupActions.deleteInstanceGroupSuccess, (state, { instanceGroup }) => { + return adapter.removeOne(instanceGroup.id, state) + }) +); + +const { + selectIds, + selectEntities, + selectAll, + selectTotal, +} = adapter.getSelectors(); + + +export const selectInstanceGroupIds = selectIds; +export const selectInstanceGroupEntities = selectEntities; +export const selectAllInstanceGroups = selectAll; +export const selectInstanceGroupTotal = selectTotal; + +export const selectInstanceGroupListIsLoading = (state: State) => state.instanceGroupListIsLoading; +export const selectInstanceGroupListIsLoaded = (state: State) => state.instanceGroupListIsLoaded; diff --git a/client/src/app/metamodel/selectors/instance-group.selector.ts b/client/src/app/metamodel/selectors/instance-group.selector.ts new file mode 100644 index 00000000..c691745e --- /dev/null +++ b/client/src/app/metamodel/selectors/instance-group.selector.ts @@ -0,0 +1,54 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createSelector } from '@ngrx/store'; + +import * as reducer from '../metamodel.reducer'; +import * as fromInstanceGroup from '../reducers/instance-group.reducer'; + +export const selectInstanceGroupState = createSelector( + reducer.getMetamodelState, + (state: reducer.State) => state.instanceGroup +); + +export const selectInstanceGroupIds = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectInstanceGroupIds +); + +export const selectInstanceGroupEntities = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectInstanceGroupEntities +); + +export const selectAllInstanceGroups = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectAllInstanceGroups +); + +export const selectInstanceGroupTotal = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectInstanceGroupTotal +); + +export const selectInstanceGroupListIsLoading = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectInstanceGroupListIsLoading +); + +export const selectInstanceGroupListIsLoaded = createSelector( + selectInstanceGroupState, + fromInstanceGroup.selectInstanceGroupListIsLoaded +); + +export const selectInstanceGroupByRouteId = createSelector( + selectInstanceGroupEntities, + reducer.selectRouterState, + (entities, router) => entities[router.state.params.id] +); diff --git a/client/src/app/metamodel/services/index.ts b/client/src/app/metamodel/services/index.ts index 407158e8..414036d9 100644 --- a/client/src/app/metamodel/services/index.ts +++ b/client/src/app/metamodel/services/index.ts @@ -12,6 +12,7 @@ import { TableService } from './table.service'; import { ColumnService } from './column.service'; import { SurveyService } from './survey.service'; import { GroupService } from './group.service'; +import { InstanceGroupService } from './instance-group.service'; import { DatasetService } from './dataset.service'; import { DatasetFamilyService } from './dataset-family.service'; import { InstanceService } from './instance.service'; @@ -30,6 +31,7 @@ export const metamodelServices = [ ColumnService, SurveyService, GroupService, + InstanceGroupService, DatasetService, DatasetFamilyService, InstanceService, diff --git a/client/src/app/metamodel/services/instance-group.service.ts b/client/src/app/metamodel/services/instance-group.service.ts new file mode 100644 index 00000000..2fbc6485 --- /dev/null +++ b/client/src/app/metamodel/services/instance-group.service.ts @@ -0,0 +1,67 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { InstanceGroup } from '../models'; +import { AppConfigService } from 'src/app/app-config.service'; + +/** + * @class + * @classdesc InstanceGroup service. + */ +@Injectable() +export class InstanceGroupService { + constructor(private http: HttpClient, private config: AppConfigService) { } + + /** + * Retrieves instance group list + * + * @return Observable<InstanceGroup[]> + */ + retrieveInstanceGroupList(): Observable<InstanceGroup[]> { + return this.http.get<InstanceGroup[]>(`${this.config.apiUrl}/instance-group`); + } + + /** + * Adds a new instance group + * + * @param {InstanceGroup} newInstanceGroup - The instance group. + * + * @return Observable<InstanceGroup> + */ + addInstanceGroup(newInstanceGroup: InstanceGroup): Observable<InstanceGroup> { + return this.http.post<InstanceGroup>(`${this.config.apiUrl}/instance-group`, newInstanceGroup); + } + + /** + * Modifies an instance group. + * + * @param {InstanceGroup} instanceGroup - The instance group. + * + * @return Observable<InstanceGroup> + */ + editInstanceGroup(instanceGroup: InstanceGroup): Observable<InstanceGroup> { + return this.http.put<InstanceGroup>(`${this.config.apiUrl}/instance-group/${instanceGroup.id}`, instanceGroup); + } + + /** + * Removes an instance group. + * + * @param {number} instanceGroupId - The instance Group ID. + * + * @return Observable<object> + */ + deleteInstanceGroup(instanceGroupId: number): Observable<object> { + return this.http.delete(`${this.config.apiUrl}/instance-group/${instanceGroupId}`); + } +} diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index 166ddda8..fe86c76e 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -60,7 +60,7 @@ curl -d '{"label":"Spectra graph","value":"spectra_graph","display":20,"select_n curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' --header 'Content-Type: application/json' -X POST http://localhost/database # Add default instance -curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance +curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance # Add ANIS, SVOM and IRIS surveys curl -d '{"name":"anis_survey","label":"ANIS survey","description":"Survey used for testing","link":"https://anis.lam.fr","manager":"F. Agneray","id_database":1}' --header 'Content-Type: application/json' -X POST http://localhost/survey diff --git a/server/app/dependencies.php b/server/app/dependencies.php index 7eb29195..d59b3c2d 100644 --- a/server/app/dependencies.php +++ b/server/app/dependencies.php @@ -21,7 +21,7 @@ $container->set(SETTINGS, function () { $container->set('em', function (ContainerInterface $c) { $settings = $c->get(SETTINGS)['database']; $devMode = boolval($settings['dev_mode']); - $proxyDir = getcwd() . '/../doctrine-proxy'; + $proxyDir = '/project/doctrine-proxy'; if ($devMode) { $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap( @@ -113,6 +113,14 @@ $container->set('App\Action\AdminFileExplorerAction', function (ContainerInterfa return new App\Action\AdminFileExplorerAction($c->get('settings')['data_path']); }); +$container->set('App\Action\InstanceGroupListAction', function (ContainerInterface $c) { + return new App\Action\InstanceGroupListAction($c->get('em')); +}); + +$container->set('App\Action\InstanceGroupAction', function (ContainerInterface $c) { + return new App\Action\InstanceGroupAction($c->get('em')); +}); + $container->set('App\Action\SurveyListAction', function (ContainerInterface $c) { return new App\Action\SurveyListAction($c->get('em')); }); diff --git a/server/app/routes.php b/server/app/routes.php index a8aa5536..6e137446 100644 --- a/server/app/routes.php +++ b/server/app/routes.php @@ -33,6 +33,8 @@ $app->group('', function (RouteCollectorProxy $group) { // Metamodel actions $app->group('', function (RouteCollectorProxy $group) { + $group->map([OPTIONS, GET, POST], '/instance-group', App\Action\InstanceGroupListAction::class); + $group->map([OPTIONS, GET, PUT, DELETE], '/instance-group/{id}', App\Action\InstanceGroupAction::class); $group->map([OPTIONS, GET, POST], '/survey', App\Action\SurveyListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/survey/{name}', App\Action\SurveyAction::class); $group->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class); diff --git a/server/doctrine-proxy/__CG__AppEntityDataset.php b/server/doctrine-proxy/__CG__AppEntityDataset.php index 857d284e..30afd80c 100644 --- a/server/doctrine-proxy/__CG__AppEntityDataset.php +++ b/server/doctrine-proxy/__CG__AppEntityDataset.php @@ -67,10 +67,10 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'config', 'public', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'config', 'public', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } /** @@ -305,56 +305,452 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy /** * {@inheritDoc} */ - public function getConfig() + public function getPublic() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConfig', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPublic', []); - return parent::getConfig(); + return parent::getPublic(); } /** * {@inheritDoc} */ - public function setConfig($config) + public function setPublic($public) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConfig', [$config]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPublic', [$public]); - return parent::setConfig($config); + return parent::setPublic($public); } /** * {@inheritDoc} */ - public function getPublic() + public function getSurvey() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPublic', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getSurvey', []); - return parent::getPublic(); + return parent::getSurvey(); } /** * {@inheritDoc} */ - public function setPublic($public) + public function getInfoSurveyEnabled() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPublic', [$public]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getInfoSurveyEnabled', []); - return parent::setPublic($public); + return parent::getInfoSurveyEnabled(); } /** * {@inheritDoc} */ - public function getSurvey() + public function setInfoSurveyEnabled($infoSurveyEnabled) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getSurvey', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setInfoSurveyEnabled', [$infoSurveyEnabled]); - return parent::getSurvey(); + return parent::setInfoSurveyEnabled($infoSurveyEnabled); + } + + /** + * {@inheritDoc} + */ + public function getInfoSurveyLabel() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getInfoSurveyLabel', []); + + return parent::getInfoSurveyLabel(); + } + + /** + * {@inheritDoc} + */ + public function setInfoSurveyLabel($infoSurveyLabel) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setInfoSurveyLabel', [$infoSurveyLabel]); + + return parent::setInfoSurveyLabel($infoSurveyLabel); + } + + /** + * {@inheritDoc} + */ + public function getConeSearchEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchEnabled', []); + + return parent::getConeSearchEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchEnabled($coneSearchEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchEnabled', [$coneSearchEnabled]); + + return parent::setConeSearchEnabled($coneSearchEnabled); + } + + /** + * {@inheritDoc} + */ + public function getConeSearchOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchOpened', []); + + return parent::getConeSearchOpened(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchOpened($coneSearchOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchOpened', [$coneSearchOpened]); + + return parent::setConeSearchOpened($coneSearchOpened); + } + + /** + * {@inheritDoc} + */ + public function getConeSearchColumnRa() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchColumnRa', []); + + return parent::getConeSearchColumnRa(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchColumnRa($coneSearchColumnRa) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchColumnRa', [$coneSearchColumnRa]); + + return parent::setConeSearchColumnRa($coneSearchColumnRa); + } + + /** + * {@inheritDoc} + */ + public function getConeSearchColumnDec() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchColumnDec', []); + + return parent::getConeSearchColumnDec(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchColumnDec($coneSearchColumnDec) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchColumnDec', [$coneSearchColumnDec]); + + return parent::setConeSearchColumnDec($coneSearchColumnDec); + } + + /** + * {@inheritDoc} + */ + public function getDownloadEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadEnabled', []); + + return parent::getDownloadEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadEnabled($downloadEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadEnabled', [$downloadEnabled]); + + return parent::setDownloadEnabled($downloadEnabled); + } + + /** + * {@inheritDoc} + */ + public function getDownloadOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadOpened', []); + + return parent::getDownloadOpened(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadOpened($downloadOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadOpened', [$downloadOpened]); + + return parent::setDownloadOpened($downloadOpened); + } + + /** + * {@inheritDoc} + */ + public function getDownloadCsv() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadCsv', []); + + return parent::getDownloadCsv(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadCsv($downloadCsv) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadCsv', [$downloadCsv]); + + return parent::setDownloadCsv($downloadCsv); + } + + /** + * {@inheritDoc} + */ + public function getDownloadAscii() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadAscii', []); + + return parent::getDownloadAscii(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadAscii($downloadAscii) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadAscii', [$downloadAscii]); + + return parent::setDownloadAscii($downloadAscii); + } + + /** + * {@inheritDoc} + */ + public function getDownloadVo() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadVo', []); + + return parent::getDownloadVo(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadVo($downloadVo) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadVo', [$downloadVo]); + + return parent::setDownloadVo($downloadVo); + } + + /** + * {@inheritDoc} + */ + public function getDownloadArchive() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadArchive', []); + + return parent::getDownloadArchive(); + } + + /** + * {@inheritDoc} + */ + public function setDownloadArchive($downloadArchive) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadArchive', [$downloadArchive]); + + return parent::setDownloadArchive($downloadArchive); + } + + /** + * {@inheritDoc} + */ + public function getSummaryEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getSummaryEnabled', []); + + return parent::getSummaryEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setSummaryEnabled($summaryEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setSummaryEnabled', [$summaryEnabled]); + + return parent::setSummaryEnabled($summaryEnabled); + } + + /** + * {@inheritDoc} + */ + public function getSummaryOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getSummaryOpened', []); + + return parent::getSummaryOpened(); + } + + /** + * {@inheritDoc} + */ + public function setSummaryOpened($summaryOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setSummaryOpened', [$summaryOpened]); + + return parent::setSummaryOpened($summaryOpened); + } + + /** + * {@inheritDoc} + */ + public function getServerLinkEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getServerLinkEnabled', []); + + return parent::getServerLinkEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setServerLinkEnabled($serverLinkEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setServerLinkEnabled', [$serverLinkEnabled]); + + return parent::setServerLinkEnabled($serverLinkEnabled); + } + + /** + * {@inheritDoc} + */ + public function getServerLinkOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getServerLinkOpened', []); + + return parent::getServerLinkOpened(); + } + + /** + * {@inheritDoc} + */ + public function setServerLinkOpened($serverLinkOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setServerLinkOpened', [$serverLinkOpened]); + + return parent::setServerLinkOpened($serverLinkOpened); + } + + /** + * {@inheritDoc} + */ + public function getDatatableEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDatatableEnabled', []); + + return parent::getDatatableEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setDatatableEnabled($datatableEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDatatableEnabled', [$datatableEnabled]); + + return parent::setDatatableEnabled($datatableEnabled); + } + + /** + * {@inheritDoc} + */ + public function getDatatableOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDatatableOpened', []); + + return parent::getDatatableOpened(); + } + + /** + * {@inheritDoc} + */ + public function setDatatableOpened($datatableOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDatatableOpened', [$datatableOpened]); + + return parent::setDatatableOpened($datatableOpened); + } + + /** + * {@inheritDoc} + */ + public function getDatatableSelectableRows() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDatatableSelectableRows', []); + + return parent::getDatatableSelectableRows(); + } + + /** + * {@inheritDoc} + */ + public function setDatatableSelectableRows($datatableSelectableRows) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDatatableSelectableRows', [$datatableSelectableRows]); + + return parent::setDatatableSelectableRows($datatableSelectableRows); } /** diff --git a/server/doctrine-proxy/__CG__AppEntityInstance.php b/server/doctrine-proxy/__CG__AppEntityInstance.php index 1a1fb771..d5f1d1d1 100644 --- a/server/doctrine-proxy/__CG__AppEntityInstance.php +++ b/server/doctrine-proxy/__CG__AppEntityInstance.php @@ -67,10 +67,10 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } /** @@ -280,6 +280,28 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy return parent::setDataPath($dataPath); } + /** + * {@inheritDoc} + */ + public function getPublic() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPublic', []); + + return parent::getPublic(); + } + + /** + * {@inheritDoc} + */ + public function setPublic($public) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPublic', [$public]); + + return parent::setPublic($public); + } + /** * {@inheritDoc} */ @@ -434,6 +456,28 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy return parent::setHomeComponentConfig($homeComponentConfig); } + /** + * {@inheritDoc} + */ + public function getSampEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getSampEnabled', []); + + return parent::getSampEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setSampEnabled($sampEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setSampEnabled', [$sampEnabled]); + + return parent::setSampEnabled($sampEnabled); + } + /** * {@inheritDoc} */ diff --git a/server/doctrine-proxy/__CG__AppEntityInstanceGroup.php b/server/doctrine-proxy/__CG__AppEntityInstanceGroup.php new file mode 100644 index 00000000..739fe6fd --- /dev/null +++ b/server/doctrine-proxy/__CG__AppEntityInstanceGroup.php @@ -0,0 +1,250 @@ +<?php + +namespace DoctrineProxies\__CG__\App\Entity; + + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class InstanceGroup extends \App\Entity\InstanceGroup implements \Doctrine\ORM\Proxy\Proxy +{ + /** + * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with + * three parameters, being respectively the proxy object to be initialized, the method that triggered the + * initialization process and an array of ordered parameters that were passed to that method. + * + * @see \Doctrine\Common\Proxy\Proxy::__setInitializer + */ + public $__initializer__; + + /** + * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object + * + * @see \Doctrine\Common\Proxy\Proxy::__setCloner + */ + public $__cloner__; + + /** + * @var boolean flag indicating if this object was already initialized + * + * @see \Doctrine\Persistence\Proxy::__isInitialized + */ + public $__isInitialized__ = false; + + /** + * @var array<string, null> properties to be lazy loaded, indexed by property name + */ + public static $lazyPropertiesNames = array ( +); + + /** + * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names + * + * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = array ( +); + + + + public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) + { + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } + + + + + + + + /** + * + * @return array + */ + public function __sleep() + { + if ($this->__isInitialized__) { + return ['__isInitialized__', 'id', 'role', 'instances']; + } + + return ['__isInitialized__', 'id', 'role', 'instances']; + } + + /** + * + */ + public function __wakeup() + { + if ( ! $this->__isInitialized__) { + $this->__initializer__ = function (InstanceGroup $proxy) { + $proxy->__setInitializer(null); + $proxy->__setCloner(null); + + $existingProperties = get_object_vars($proxy); + + foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) { + if ( ! array_key_exists($property, $existingProperties)) { + $proxy->$property = $defaultValue; + } + } + }; + + } + } + + /** + * + */ + public function __clone() + { + $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []); + } + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @deprecated no longer in use - generated code now relies on internal components rather than generated public API + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + + /** + * {@inheritDoc} + */ + public function getId() + { + if ($this->__isInitialized__ === false) { + return (int) parent::getId(); + } + + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []); + + return parent::getId(); + } + + /** + * {@inheritDoc} + */ + public function getRole() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getRole', []); + + return parent::getRole(); + } + + /** + * {@inheritDoc} + */ + public function setRole($role) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setRole', [$role]); + + return parent::setRole($role); + } + + /** + * {@inheritDoc} + */ + public function getInstances() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getInstances', []); + + return parent::getInstances(); + } + + /** + * {@inheritDoc} + */ + public function setInstances($instances) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setInstances', [$instances]); + + return parent::setInstances($instances); + } + + /** + * {@inheritDoc} + */ + public function jsonSerialize(): array + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []); + + return parent::jsonSerialize(); + } + +} diff --git a/server/src/Action/InstanceAction.php b/server/src/Action/InstanceAction.php index 750bd1b7..7f2b5789 100644 --- a/server/src/Action/InstanceAction.php +++ b/server/src/Action/InstanceAction.php @@ -99,6 +99,7 @@ final class InstanceAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); $instance->setDesignBackgroundColor($parsedBody['design_background_color']); diff --git a/server/src/Action/InstanceGroupAction.php b/server/src/Action/InstanceGroupAction.php new file mode 100644 index 00000000..ae7d2688 --- /dev/null +++ b/server/src/Action/InstanceGroupAction.php @@ -0,0 +1,125 @@ +<?php + +/* + * This file is part of Anis Server. + * + * (c) Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace App\Action; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Slim\Exception\HttpBadRequestException; +use Slim\Exception\HttpNotFoundException; +use App\Entity\InstanceGroup; +use App\Entity\Instance; + +/** + * @author François Agneray <francois.agneray@lam.fr> + * @package App\Action + */ +final class InstanceGroupAction extends AbstractAction +{ + /** + * `GET` Returns the InstanceGroup found + * `PUT` Full update the InstanceGroup and returns the new version + * `DELETE` Delete the InstanceGroup found and return a confirmation message + * + * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request + * @param ResponseInterface $response PSR-7 This object represents the HTTP response + * @param string[] $args This table contains information transmitted in the URL (see routes.php) + * + * @return ResponseInterface + */ + public function __invoke( + ServerRequestInterface $request, + ResponseInterface $response, + array $args + ): ResponseInterface { + if ($request->getMethod() === OPTIONS) { + return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS'); + } + + // Search the correct instance-group with primary key + $instanceGroup = $this->em->find('App\Entity\InstanceGroup', $args['id']); + + // If group is not found 404 + if (is_null($instanceGroup)) { + throw new HttpNotFoundException( + $request, + 'Instance-group with id ' . $args['id'] . ' is not found' + ); + } + + if ($request->getMethod() === GET) { + $payload = json_encode($instanceGroup); + } + + if ($request->getMethod() === PUT) { + $parsedBody = $request->getParsedBody(); + + // If mandatories empty fields 400 + foreach (array('role', 'instances') as $a) { + if ($this->isEmptyField($a, $parsedBody)) { + throw new HttpBadRequestException( + $request, + 'Param ' . $a . ' needed to edit the instance-group' + ); + } + } + + $this->editInstanceGroup($instanceGroup, $parsedBody); + $payload = json_encode($instanceGroup); + } + + if ($request->getMethod() === DELETE) { + $id = $instanceGroup->getId(); + $this->em->remove($instanceGroup); + $this->em->flush(); + $payload = json_encode(array('message' => 'Instance-group with id ' . $id . ' is removed!')); + } + + $response->getBody()->write($payload); + return $response; + } + + /** + * Update instance-group object with setters + * + * @param InstanceGroup $instanceGroup The instance-group to update + * @param array $parsedBody Contains the new values ​​of the instance-group sent by the user + */ + private function editInstanceGroup(InstanceGroup $instanceGroup, array $parsedBody): void + { + $instanceGroup->setRole($parsedBody['role']); + $instanceGroup->setInstances($this->getInstances($parsedBody['instances'])); + $this->em->flush(); + } + + /** + * Retrieves list of instances by list of instances names + * + * @param string[] $listOfInstancesNames List of instances names + * + * @return Instance[] List of instances found + */ + private function getInstances(array $listOfInstancesNames): array + { + if (count($listOfInstancesNames) < 1) { + return array(); + } + + $in = implode(',', array_map(function ($d) { + return "'" . $d . "'"; + }, $listOfInstancesNames)); + + $dql = 'SELECT i FROM App\Entity\Instance i WHERE i.name IN (' . $in . ')'; + $query = $this->em->createQuery($dql); + return $query->getResult(); + } +} diff --git a/server/src/Action/InstanceGroupListAction.php b/server/src/Action/InstanceGroupListAction.php new file mode 100644 index 00000000..ae7760ff --- /dev/null +++ b/server/src/Action/InstanceGroupListAction.php @@ -0,0 +1,113 @@ +<?php + +/* + * This file is part of Anis Server. + * + * (c) Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace App\Action; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Slim\Exception\HttpBadRequestException; +use App\Entity\InstanceGroup; +use App\Entity\Instance; + +/** + * @author François Agneray <francois.agneray@lam.fr> + * @package App\Action + */ +final class InstanceGroupListAction extends AbstractAction +{ + /** + * `GET` Returns a list of all instance-groups listed in the metamodel database + * `POST` Add a new instance-group + * + * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request + * @param ResponseInterface $response PSR-7 This object represents the HTTP response + * @param string[] $args This table contains information transmitted in the URL (see routes.php) + * + * @return ResponseInterface + */ + public function __invoke( + ServerRequestInterface $request, + ResponseInterface $response, + array $args + ): ResponseInterface { + if ($request->getMethod() === OPTIONS) { + return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + } + + if ($request->getMethod() === GET) { + $instanceGroups = $this->em->getRepository('App\Entity\InstanceGroup')->findAll(); + $payload = json_encode($instanceGroups); + } + + if ($request->getMethod() === POST) { + $parsedBody = $request->getParsedBody(); + + // To work this action needs instance-group information + foreach (array('role', 'instances') as $a) { + if ($this->isEmptyField($a, $parsedBody)) { + throw new HttpBadRequestException( + $request, + 'Param ' . $a . ' needed to add a new instance-group' + ); + } + } + + $instanceGroup = $this->postInstanceGroup($parsedBody); + $payload = json_encode($instanceGroup); + $response = $response->withStatus(201); + } + + $response->getBody()->write($payload); + return $response; + } + + /** + * Add a new instance-group into the metamodel + * + * @param array $parsedBody Contains the values ​​of the new instance-group sent by the user + * + * @return InstanceGroup The newly created instance-group + */ + private function postInstanceGroup(array $parsedBody): InstanceGroup + { + $instanceGroup = new InstanceGroup(); + $instanceGroup->setRole($parsedBody['role']); + $instanceGroup->setInstances($this->getInstances($parsedBody['instances'])); + + $this->em->persist($instanceGroup); + $this->em->flush(); + + return $instanceGroup; + } + + /** + * Retrieves list of instances by list of instances names + * + * @param string[] $listOfInstancesNames List of instances names + * + * @return Instance[] List of instances found + */ + private function getInstances(array $listOfInstancesNames): array + { + if (count($listOfInstancesNames) < 1) { + return array(); + } + + $in = implode(',', array_map(function ($d) { + return "'" . $d . "'"; + }, $listOfInstancesNames)); + + $dql = 'SELECT i FROM App\Entity\Instance i WHERE i.name IN (' . $in . ')'; + $query = $this->em->createQuery($dql); + return $query->getResult(); + } +} diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php index 628961d4..9aa4c2a9 100644 --- a/server/src/Action/InstanceListAction.php +++ b/server/src/Action/InstanceListAction.php @@ -82,6 +82,7 @@ final class InstanceListAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); $instance->setDesignBackgroundColor($parsedBody['design_background_color']); diff --git a/server/src/Entity/Instance.php b/server/src/Entity/Instance.php index 213386f3..044e3261 100644 --- a/server/src/Entity/Instance.php +++ b/server/src/Entity/Instance.php @@ -24,7 +24,7 @@ use Doctrine\Common\Collections\ArrayCollection; class Instance implements \JsonSerializable { /** - * @var int + * @var string * * @Id * @Column(type="string", nullable=false) @@ -59,6 +59,13 @@ class Instance implements \JsonSerializable */ protected $dataPath; + /** + * @var bool + * + * @Column(type="boolean", nullable=false) + */ + protected $public; + /** * @var string * @@ -223,6 +230,16 @@ class Instance implements \JsonSerializable $this->dataPath = $dataPath; } + public function getPublic() + { + return $this->public; + } + + public function setPublic($public) + { + $this->public = $public; + } + public function getPortalLogo() { return $this->portalLogo; @@ -395,6 +412,7 @@ class Instance implements \JsonSerializable 'description' => $this->getDescription(), 'display' => $this->getDisplay(), 'data_path' => $this->getDataPath(), + 'public' => $this->getPublic(), 'portal_logo' => $this->getPortalLogo(), 'design_color' => $this->getDesignColor(), 'design_background_color' => $this->getDesignBackgroundColor(), diff --git a/server/src/Entity/InstanceGroup.php b/server/src/Entity/InstanceGroup.php new file mode 100644 index 00000000..35423f29 --- /dev/null +++ b/server/src/Entity/InstanceGroup.php @@ -0,0 +1,92 @@ +<?php + +/* + * This file is part of Anis Server. + * + * (c) Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace App\Entity; + +/** + * @author François Agneray <francois.agneray@lam.fr> + * @package App\Entity + * + * @Entity + * @Table(name="instance_group") + */ +class InstanceGroup implements \JsonSerializable +{ + /** + * @var int + * + * @Id + * @Column(type="integer", nullable=false) + * @GeneratedValue + */ + protected $id; + + /** + * @var string + * + * @Column(type="string", nullable=false) + */ + protected $role; + + /** + * @var Instance[] + * + * Many Groups have Many Instances privileges. + * + * @ManyToMany(targetEntity="Instance") + * @JoinTable( + * name="instance_groups_datasets", + * joinColumns={@JoinColumn(name="instance_group_id", referencedColumnName="id", onDelete="CASCADE")}, + * inverseJoinColumns={@JoinColumn(name="instance_name", referencedColumnName="name", onDelete="CASCADE")} + * ) + */ + protected $instances; + + public function getId() + { + return $this->id; + } + + public function getRole() + { + return $this->role; + } + + public function setRole($role) + { + $this->role = $role; + } + + public function getInstances() + { + return $this->instances; + } + + public function setInstances($instances) + { + $this->instances = $instances; + } + + public function jsonSerialize(): array + { + $instanceNames = array(); + foreach ($this->getInstances() as $instance) { + $instanceNames[] = $instance->getName(); + } + + return [ + 'id' => $this->getId(), + 'role' => $this->getRole(), + 'instances' => $instanceNames + ]; + } +} -- GitLab