diff --git a/AMW_angular/io/.eslintrc.json b/AMW_angular/io/.eslintrc.json index d77d6de87..fd4ffce00 100644 --- a/AMW_angular/io/.eslintrc.json +++ b/AMW_angular/io/.eslintrc.json @@ -16,7 +16,7 @@ "error", { "type": "attribute", - "prefix": "amw", + "prefix": "app", "style": "camelCase" } ], @@ -24,7 +24,7 @@ "error", { "type": "element", - "prefix": "amw", + "prefix": "app", "style": "kebab-case" } ] diff --git a/AMW_angular/io/coding-guidelines.md b/AMW_angular/io/coding-guidelines.md index 023f9d09f..a487dfa43 100644 --- a/AMW_angular/io/coding-guidelines.md +++ b/AMW_angular/io/coding-guidelines.md @@ -1,6 +1,5 @@ # Coding Guidelines - ## Inject() over Constructor-Injections Use `inject()` instead of constuctor-injection to make the code more explicit and obvious. @@ -16,6 +15,7 @@ constructor( ``` ## RxJS + Leverage RxJS for API calls, web sockets, and complex data flows, especially when handling multiple asynchronous events. Combine with Signals to simplify component state management. ## Signals @@ -56,7 +56,7 @@ readOnlyUsers = toSignal(this.users$, { initialValue: [] as User[]}); ## Auth Service -The frontend provides a singelton auth-service which holds all restrictions for the current user. +The frontend provides a singelton auth-service which holds all restrictions for the current user. After injecting the service in your component you can get Permissions/Actions depending on your needs: @@ -72,5 +72,4 @@ this.canCreate.set(actions.some(isAllowed('CREATE'))); // or directly set signal based on a concret permission and action value this.canViewSettings.set(this.authService.hasPermission('SETTINGS', 'READ')); - ``` diff --git a/AMW_angular/io/src/app/app.component.spec.ts b/AMW_angular/io/src/app/app.component.spec.ts index 7b5a06b20..3b08a4c16 100644 --- a/AMW_angular/io/src/app/app.component.spec.ts +++ b/AMW_angular/io/src/app/app.component.spec.ts @@ -1,7 +1,6 @@ import { ChangeDetectorRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { provideRouter, Router, withHashLocation } from '@angular/router'; import { of } from 'rxjs'; import { AppComponent } from './app.component'; import { NavigationComponent } from './navigation/navigation.component'; @@ -10,6 +9,7 @@ import { AppConfiguration } from './setting/app-configuration'; import { SettingService } from './setting/setting.service'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { routes } from './app.routes'; class RouterStub { navigateByUrl(url: string) { @@ -18,17 +18,23 @@ class RouterStub { } describe('App', () => { - let router: Router; let app: AppComponent; let fixture: ComponentFixture; let settingService: SettingService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule, NavigationComponent, AppComponent], - providers: [SettingService, ChangeDetectorRef, AppComponent, { provide: Router, useClass: RouterStub }, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}).compileComponents(); - router = TestBed.inject(Router); + imports: [NavigationComponent, AppComponent], + providers: [ + SettingService, + ChangeDetectorRef, + AppComponent, + { provide: Router, useClass: RouterStub }, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + provideRouter(routes, withHashLocation()), + ], + }).compileComponents(); settingService = TestBed.inject(SettingService); fixture = TestBed.createComponent(AppComponent); @@ -54,7 +60,6 @@ describe('App', () => { it('should set empty logoutUrl if config not found', () => { // given - const expectedKey: string = 'logoutUrl'; const expectedValue: string = ''; const appConf: AppConfiguration = { key: { value: 'test', env: 'TEST' }, @@ -63,6 +68,6 @@ describe('App', () => { app.ngOnInit(); - expect(app.logoutUrl).toEqual(''); + expect(app.logoutUrl).toEqual(expectedValue); }); }); diff --git a/AMW_angular/io/src/app/app.component.ts b/AMW_angular/io/src/app/app.component.ts index c72601d1c..2c0f43850 100644 --- a/AMW_angular/io/src/app/app.component.ts +++ b/AMW_angular/io/src/app/app.component.ts @@ -18,9 +18,7 @@ import { ToastContainerComponent } from './shared/elements/toast/toast-container export class AppComponent implements OnInit { logoutUrl: string; - constructor( - private settingService: SettingService - ) { } + constructor(private settingService: SettingService) {} ngOnInit(): void { this.settingService.getAllAppSettings().subscribe((r) => this.configureSettings(r)); diff --git a/AMW_angular/io/src/app/app.routes.ts b/AMW_angular/io/src/app/app.routes.ts index 3c9cc8025..c5c059564 100644 --- a/AMW_angular/io/src/app/app.routes.ts +++ b/AMW_angular/io/src/app/app.routes.ts @@ -1,5 +1,6 @@ import { Routes } from '@angular/router'; import { DeploymentsComponent } from './deployments/deployments.component'; +import { appsRoutes } from './apps/apps.routes'; import { auditviewRoutes } from './auditview/auditview.routes'; import { deploymentRoutes } from './deployment/deployment-routes'; import { settingsRoutes } from './settings/settings.routes'; @@ -9,6 +10,7 @@ export const routes: Routes = [ // default route only, the rest is done in module routing { path: '', component: DeploymentsComponent }, + ...appsRoutes, ...settingsRoutes, ...auditviewRoutes, ...deploymentRoutes, diff --git a/AMW_angular/io/src/app/apps/app-add/app-add.component.html b/AMW_angular/io/src/app/apps/app-add/app-add.component.html new file mode 100644 index 000000000..0305e432b --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-add/app-add.component.html @@ -0,0 +1,55 @@ + + + diff --git a/AMW_angular/io/src/app/apps/app-add/app-add.component.spec.ts b/AMW_angular/io/src/app/apps/app-add/app-add.component.spec.ts new file mode 100644 index 000000000..88f171596 --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-add/app-add.component.spec.ts @@ -0,0 +1,15 @@ +import { AppAddComponent } from './app-add.component'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +describe('AppAddComponent', () => { + let component: AppAddComponent; + const activeModal = new NgbActiveModal(); + + beforeEach(async () => { + component = new AppAddComponent(activeModal); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/app-add/app-add.component.ts b/AMW_angular/io/src/app/apps/app-add/app-add.component.ts new file mode 100644 index 000000000..bc1907e1a --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-add/app-add.component.ts @@ -0,0 +1,61 @@ +import { Component, EventEmitter, Input, Output, Signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { Release } from '../../settings/releases/release'; +import { Release as Rel } from '../../resource/release'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Resource } from '../../resource/resource'; +import { AppCreate } from '../app-create'; + +@Component({ + selector: 'app-app-add', + standalone: true, + imports: [FormsModule, NgSelectModule], + templateUrl: './app-add.component.html', +}) +export class AppAddComponent { + @Input() releases: Signal; + @Input() appServerGroups: Signal; + @Output() saveApp: EventEmitter = new EventEmitter(); + + app: AppCreate = { appName: '', appReleaseId: null, appServerId: null, appServerReleaseId: null }; + appServerGroup: Resource; + appServerRelease: Rel; + + constructor(public activeModal: NgbActiveModal) { + this.activeModal = activeModal; + } + + hasInvalidGroup(): boolean { + const isInvalid = + this.appServerGroup === undefined || this.appServerGroup === null || this.appServerGroup?.releases.length === 0; + if (isInvalid) { + this.appServerRelease = undefined; + } + return isInvalid; + } + + // apps without appserver are valid too + hasInvalidFields(): boolean { + return ( + this.app.appName === '' || + this.app.appReleaseId === null || + (!this.hasInvalidGroup() && (this.appServerRelease === undefined || this.appServerRelease === null)) + ); + } + + cancel() { + this.activeModal.close(); + } + + save() { + const app: AppCreate = { + appName: this.app.appName, + appReleaseId: this.app.appReleaseId, + appServerId: this.appServerGroup?.id, + appServerReleaseId: this.appServerRelease?.id, + }; + this.saveApp.emit(app); + this.activeModal.close(); + } +} diff --git a/AMW_angular/io/src/app/apps/app-create.ts b/AMW_angular/io/src/app/apps/app-create.ts new file mode 100644 index 000000000..b9dd628d2 --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-create.ts @@ -0,0 +1,6 @@ +export interface AppCreate { + appName: string; + appReleaseId: number; + appServerId: number; + appServerReleaseId: number; +} diff --git a/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.html b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.html new file mode 100644 index 000000000..75a5ebaea --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.html @@ -0,0 +1,26 @@ + + + diff --git a/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.spec.ts b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.spec.ts new file mode 100644 index 000000000..537cd92c5 --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.spec.ts @@ -0,0 +1,15 @@ +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { AppServerAddComponent } from './app-server-add.component'; + +describe('AppServerAddComponent', () => { + let component: AppServerAddComponent; + const activeModal = new NgbActiveModal(); + + beforeEach(async () => { + component = new AppServerAddComponent(activeModal); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.ts b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.ts new file mode 100644 index 000000000..535bbea5f --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-server-add/app-server-add.component.ts @@ -0,0 +1,44 @@ +import { Component, EventEmitter, Input, Output, Signal } from '@angular/core'; +import { Release } from '../../settings/releases/release'; +import { AppServer } from '../app-server'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; +import { NgSelectModule } from '@ng-select/ng-select'; + +@Component({ + selector: 'app-server-add', + standalone: true, + imports: [FormsModule, NgSelectModule], + templateUrl: './app-server-add.component.html', +}) +export class AppServerAddComponent { + @Input() releases: Signal; + @Output() saveAppServer: EventEmitter = new EventEmitter(); + + appServer: AppServer = { name: '', apps: [], deletable: false, id: null, runtimeName: '', release: null }; + + constructor(public activeModal: NgbActiveModal) { + this.activeModal = activeModal; + } + + hasInvalidFields(): boolean { + return this.appServer.name === '' || this.appServer.release?.id === null || this.appServer.release?.name === ''; + } + + cancel() { + this.activeModal.close(); + } + + save() { + const appServer: AppServer = { + name: this.appServer.name, + release: this.appServer.release, + deletable: this.appServer.deletable, + id: this.appServer.id, + runtimeName: this.appServer.runtimeName, + apps: this.appServer.apps, + }; + this.saveAppServer.emit(appServer); + this.activeModal.close(); + } +} diff --git a/AMW_angular/io/src/app/apps/app-server.ts b/AMW_angular/io/src/app/apps/app-server.ts new file mode 100644 index 000000000..dd80acef9 --- /dev/null +++ b/AMW_angular/io/src/app/apps/app-server.ts @@ -0,0 +1,11 @@ +import { Release } from '../settings/releases/release'; +import { App } from './app'; + +export interface AppServer { + id: number; + name: string; + deletable: boolean; + runtimeName: string; + release: Release; + apps: App[]; +} diff --git a/AMW_angular/io/src/app/apps/app.ts b/AMW_angular/io/src/app/apps/app.ts new file mode 100644 index 000000000..a75f9c0b4 --- /dev/null +++ b/AMW_angular/io/src/app/apps/app.ts @@ -0,0 +1,7 @@ +import { Release } from '../settings/releases/release'; + +export interface App { + id: number; + name: string; + release: Release; +} diff --git a/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.html b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.html new file mode 100644 index 000000000..60ca6cfe0 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.html @@ -0,0 +1,24 @@ +
+
+
+ Add Filter +
+
+
+
+ + +
+
+ + + @for (release of releases; track release.id) { + {{ release.name }} + } + +
+
+ +
+
+
diff --git a/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.spec.ts b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.spec.ts new file mode 100644 index 000000000..0ea6208af --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AppsFilterComponent } from './apps-filter.component'; + +describe('AppsFilterComponent', () => { + let component: AppsFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppsFilterComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AppsFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.ts b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.ts new file mode 100644 index 000000000..a8dac7051 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-filter/apps-filter.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; + +import { NgSelectModule } from '@ng-select/ng-select'; +import { Release } from '../../settings/releases/release'; +import { FormsModule } from '@angular/forms'; +import { Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-apps-filter', + standalone: true, + imports: [FormsModule, NgSelectModule], + templateUrl: './apps-filter.component.html', +}) +export class AppsFilterComponent { + @Input() releases: Release[]; + + @Output() filterEvent = new EventEmitter<{ filter: string; releaseId: number }>(); + + releaseId: number = 50; + appName: string; + + search() { + this.filterEvent.emit({ filter: this.appName, releaseId: this.releaseId }); + } +} diff --git a/AMW_angular/io/src/app/apps/apps-list/apps-list-component.ts b/AMW_angular/io/src/app/apps/apps-list/apps-list-component.ts new file mode 100644 index 000000000..f523b43ec --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-list/apps-list-component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; +import { App } from '../app'; + +@Component({ + selector: 'app-apps-list', + standalone: true, + imports: [AsyncPipe, PaginationComponent], + templateUrl: './apps-list.component.html', + styleUrl: './apps-list.component.scss', +}) +export class AppsListComponent { + @Input() apps: App[]; + @Input() even: boolean; +} diff --git a/AMW_angular/io/src/app/apps/apps-list/apps-list.component.html b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.html new file mode 100644 index 000000000..25527fb3d --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.html @@ -0,0 +1,16 @@ +@if (apps && apps.length > 0) { + + + @for (app of apps; track app) { + + + + + } + +
+ {{ app.name }} + + {{ app.release.name }} +
+} diff --git a/AMW_angular/io/src/app/apps/apps-list/apps-list.component.scss b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.scss new file mode 100644 index 000000000..d6d827123 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.scss @@ -0,0 +1,6 @@ +table .w-10 { + width: 10%; +} +table .w-90 { + width: 90%; +} diff --git a/AMW_angular/io/src/app/apps/apps-list/apps-list.component.spec.ts b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.spec.ts new file mode 100644 index 000000000..2c95fe1fb --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-list/apps-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AppsListComponent } from './apps-list-component'; + +describe('AppsListComponent', () => { + let component: AppsListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppsListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AppsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.html b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.html new file mode 100644 index 000000000..6bcc2effc --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.html @@ -0,0 +1,37 @@ +
+ @if (appServers && appServers.length > 0) { + + + + + + + + + @for (appServer of appServers; track appServer; let even = $even) { + + + + + + @if (appServer.apps && appServer.apps.length > 0) { + + + + } } + +
App NameRelease
+ @if (!appServer.deletable) { +
{{ appServer.name }}
+ } @else { + {{ appServer.name }} [ + {{ appServer.runtimeName }} ] } +
+ {{ + appServer.release.name + }} +
+ +
+ } +
diff --git a/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.scss b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.scss new file mode 100644 index 000000000..d6d827123 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.scss @@ -0,0 +1,6 @@ +table .w-10 { + width: 10%; +} +table .w-90 { + width: 90%; +} diff --git a/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.spec.ts b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.spec.ts new file mode 100644 index 000000000..0f5284f14 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AppsServersListComponent } from './apps-servers-list.component'; + +describe('AppsListComponent', () => { + let component: AppsServersListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppsServersListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AppsServersListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.ts b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.ts new file mode 100644 index 000000000..2cbe6ebae --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps-servers-list/apps-servers-list.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; +import { AppServer } from '../app-server'; +import { AppsListComponent } from '../apps-list/apps-list-component'; + +@Component({ + selector: 'app-apps-servers-list', + standalone: true, + imports: [AsyncPipe, AppsListComponent, PaginationComponent], + templateUrl: './apps-servers-list.component.html', + styleUrl: './apps-servers-list.component.scss', +}) +export class AppsServersListComponent { + @Input() appServers: AppServer[]; +} diff --git a/AMW_angular/io/src/app/apps/apps.component.html b/AMW_angular/io/src/app/apps/apps.component.html new file mode 100644 index 000000000..bad9aee67 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps.component.html @@ -0,0 +1,50 @@ + + +
Apps
+
+ {{ loadingPermissions() }} +
+
+
+
+
+
+ Application servers and applications +
+
+ @if (canCreateApp) { + + } @if (canCreateAppServer) { + + } +
+
+
+ @if (canViewAppList) { +
+ + +
+ + } +
+
+
+
+
diff --git a/AMW_angular/io/src/app/apps/apps.component.spec.ts b/AMW_angular/io/src/app/apps/apps.component.spec.ts new file mode 100644 index 000000000..01d44bdf1 --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AppsComponent } from './apps.component'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; + +describe('AppsComponent', () => { + let component: AppsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppsComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(AppsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/apps/apps.component.ts b/AMW_angular/io/src/app/apps/apps.component.ts new file mode 100644 index 000000000..7c452038e --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps.component.ts @@ -0,0 +1,173 @@ +import { Component, computed, inject, OnInit, Signal } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { LoadingIndicatorComponent } from '../shared/elements/loading-indicator.component'; +import { AsyncPipe } from '@angular/common'; +import { IconComponent } from '../shared/icon/icon.component'; +import { PageComponent } from '../layout/page/page.component'; +import { AppsFilterComponent } from './apps-filter/apps-filter.component'; +import { AuthService } from '../auth/auth.service'; +import { ReleasesService } from '../settings/releases/releases.service'; +import { AppsService } from './apps.service'; +import { Release } from '../settings/releases/release'; +import { takeUntil } from 'rxjs/operators'; +import { ToastService } from '../shared/elements/toast/toast.service'; +import { AppServer } from './app-server'; +import { AppsServersListComponent } from './apps-servers-list/apps-servers-list.component'; +import { PaginationComponent } from '../shared/pagination/pagination.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { AppServerAddComponent } from './app-server-add/app-server-add.component'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { AppAddComponent } from './app-add/app-add.component'; +import { ResourceService } from '../resource/resource.service'; +import { Resource } from '../resource/resource'; +import { AppCreate } from './app-create'; + +@Component({ + selector: 'app-apps', + standalone: true, + imports: [ + AppsFilterComponent, + AppsServersListComponent, + AsyncPipe, + IconComponent, + LoadingIndicatorComponent, + PageComponent, + PaginationComponent, + ], + templateUrl: './apps.component.html', +}) +export class AppsComponent implements OnInit { + private appsService = inject(AppsService); + private authService = inject(AuthService); + private modalService = inject(NgbModal); + private releaseService = inject(ReleasesService); // getCount -> getReleases(0, count) + private resourceService = inject(ResourceService); + private toastService = inject(ToastService); + + releases: Signal = toSignal(this.releaseService.getReleases(0, 50), { initialValue: [] as Release[] }); + appServerGroups = toSignal(this.resourceService.getByType('APPLICATIONSERVER'), { + initialValue: [] as Resource[], + }); + appServers = this.appsService.apps; + count = this.appsService.count; + maxResults = this.appsService.limit; + offset = this.appsService.offset; + filter = this.appsService.filter; + releaseId = this.appsService.releaseId; + private error$ = new BehaviorSubject(''); + private destroy$ = new Subject(); + + canCreateApp = false; + canCreateAppServer = false; + canViewAppList = false; + isLoading = false; + + currentPage: number; + lastPage: number; + + loadingPermissions = computed(() => { + if (this.authService.restrictions().length > 0) { + this.getUserPermissions(); + } else { + return `
Could not load permissions
`; + } + }); + + ngOnInit(): void { + this.error$.pipe(takeUntil(this.destroy$)).subscribe((msg) => { + msg !== '' ? this.toastService.error(msg) : null; + }); + this.isLoading = true; + this.setPagination(); + this.isLoading = false; + } + + private setPagination() { + this.currentPage = Math.floor(this.offset() / this.maxResults()) + 1; + this.lastPage = Math.ceil(this.count() / this.maxResults()); + } + + private getUserPermissions() { + this.canCreateApp = this.authService.hasResourcePermission('RESOURCE', 'CREATE', 'APPLICATION'); + this.canCreateAppServer = this.authService.hasResourcePermission('RESOURCE', 'CREATE', 'APPLICATIONSERVER'); + this.canViewAppList = this.authService.hasPermission('APP_AND_APPSERVER_LIST', 'READ'); + } + + addApp() { + const modalRef = this.modalService.open(AppAddComponent); + modalRef.componentInstance.releases = this.releases; + modalRef.componentInstance.appServerGroups = this.appServerGroups; + modalRef.componentInstance.saveApp.pipe(takeUntil(this.destroy$)).subscribe((app: AppCreate) => this.saveApp(app)); + } + + addServer() { + const modalRef = this.modalService.open(AppServerAddComponent); + modalRef.componentInstance.releases = this.releases; + modalRef.componentInstance.saveAppServer + .pipe(takeUntil(this.destroy$)) + .subscribe((appServer: AppServer) => this.saveAppServer(appServer)); + } + + saveAppServer(appServer: AppServer) { + this.isLoading = true; + this.appsService + .createAppServer(appServer) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: () => { + this.toastService.success('AppServer saved successfully.'); + }, + error: (e) => { + this.error$.next(e.toString()); + }, + complete: () => { + this.appsService.refreshData(); + this.isLoading = false; + }, + }); + } + + saveApp(app: AppCreate) { + this.isLoading = true; + this.appsService + .createApp(app) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: () => this.toastService.success('App saved successfully.'), + error: (e) => this.error$.next(e.toString()), + complete: () => { + this.appsService.refreshData(); + this.isLoading = false; + }, + }); + } + + setMaxResultsPerPage(max: number) { + this.maxResults.set(max); + this.offset.set(0); + this.appsService.refreshData(); + this.setPagination(); + } + + setNewOffset(offset: number) { + this.offset.set(offset); + this.appsService.refreshData(); + this.setPagination(); + } + + updateFilter(values: { filter: string; releaseId: number }) { + let update = false; + if (values.filter !== undefined && this.filter() !== values.filter) { + this.filter.set(values.filter); + update = true; + } + + if (values.releaseId > 0 && this.releaseId() !== values.releaseId) { + this.releaseId.set(values.releaseId); + update = true; + } + if (update) { + this.appsService.refreshData(); + } + } +} diff --git a/AMW_angular/io/src/app/apps/apps.routes.ts b/AMW_angular/io/src/app/apps/apps.routes.ts new file mode 100644 index 000000000..ce28c07bb --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps.routes.ts @@ -0,0 +1,3 @@ +import { AppsComponent } from './apps.component'; + +export const appsRoutes = [{ path: 'apps', component: AppsComponent }]; diff --git a/AMW_angular/io/src/app/apps/apps.service.ts b/AMW_angular/io/src/app/apps/apps.service.ts new file mode 100644 index 000000000..28d72b57e --- /dev/null +++ b/AMW_angular/io/src/app/apps/apps.service.ts @@ -0,0 +1,90 @@ +import { inject, Injectable, signal, WritableSignal } from '@angular/core'; +import { BaseService } from '../base/base.service'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable, of, startWith, Subject } from 'rxjs'; +import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; +import { AppServer } from './app-server'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { AppCreate } from './app-create'; + +@Injectable({ providedIn: 'root' }) +export class AppsService extends BaseService { + private http = inject(HttpClient); + private appsUrl = `${this.getBaseUrl()}/apps`; + + private reload$ = new Subject(); + + offset = signal(0); + limit = signal(20); + filter = signal(null); + releaseId: WritableSignal = signal(undefined); + private apps$: Observable = this.reload$.pipe( + startWith(null), + switchMap(() => this.getApps(this.offset(), this.limit(), this.filter(), this.releaseId())), + shareReplay(1), + ); + count = signal(0); + apps = toSignal(this.apps$, { initialValue: [] as AppServer[] }); + + constructor() { + super(); + } + + refreshData() { + this.reload$.next([]); + } + + private getApps(offset: number, limit: number, filter: string, releaseId: number | undefined) { + if (!releaseId) return of([]); + + let urlParams = ''; + if (offset != null) { + urlParams = `start=${offset}&`; + } + + if (limit != null) { + urlParams += `limit=${limit}&`; + } + + if (filter != null) { + urlParams += `appServerName=${filter}&`; + } + + return this.http + .get(`${this.appsUrl}?${urlParams}releaseId=${releaseId}`, { + headers: this.getHeaders(), + observe: 'response', + }) + .pipe(catchError(this.handleError)) + .pipe( + map((response: HttpResponse) => { + this.count.set(Number(response.headers.get('x-total-count'))); + return response.body; + }), + ); + } + + createAppServer(appServer: AppServer) { + return this.http + .post(`${this.appsUrl}/appServer?appServerName=${appServer.name}&releaseId=${appServer.release.id}`, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + createApp(app: AppCreate) { + if (app.appServerId) { + return this.http + .post(`${this.appsUrl}/appWithServer`, app, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + return this.http + .post(`${this.appsUrl}?appName=${app.appName}&releaseId=${app.appReleaseId}`, app, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } +} diff --git a/AMW_angular/io/src/app/auditview/auditview.component.ts b/AMW_angular/io/src/app/auditview/auditview.component.ts index 4467cbb0c..fa101595e 100644 --- a/AMW_angular/io/src/app/auditview/auditview.component.ts +++ b/AMW_angular/io/src/app/auditview/auditview.component.ts @@ -11,7 +11,7 @@ import { AuditviewTableService } from './auditview-table/auditview-table.service import { PageComponent } from '../layout/page/page.component'; @Component({ - selector: 'amw-auditview', + selector: 'app-auditview', templateUrl: './auditview.component.html', standalone: true, providers: [AuditviewService, AuditviewTableService, DatePipe], diff --git a/AMW_angular/io/src/app/auth/auth.service.ts b/AMW_angular/io/src/app/auth/auth.service.ts index 5894bce93..ef5593823 100644 --- a/AMW_angular/io/src/app/auth/auth.service.ts +++ b/AMW_angular/io/src/app/auth/auth.service.ts @@ -5,6 +5,7 @@ import { Observable, startWith, Subject } from 'rxjs'; import { catchError, shareReplay, switchMap } from 'rxjs/operators'; import { Restriction } from '../settings/permission/restriction'; import { toSignal } from '@angular/core/rxjs-interop'; +import { DefaultResourceType } from './defaultResourceType'; @Injectable({ providedIn: 'root' }) export class AuthService extends BaseService { @@ -44,6 +45,22 @@ export class AuthService extends BaseService { this.getActionsForPermission(permissionName).find((value) => value === 'ALL' || value === action) !== undefined ); } + + hasResourcePermission(permissionName: string, action: string, resourceType: string): boolean { + return ( + this.restrictions() + .filter((entry) => entry.permission.name === permissionName) + .filter((entry) => entry.resourceTypeName === resourceType || this.isDefaultType(entry, resourceType)) + .map((entry) => entry.action) + .find((entry) => entry === 'ALL' || entry === action) !== undefined + ); + } + + private isDefaultType(entry: Restriction, resourceType: string) { + if (entry.resourceTypeName === null && entry.resourceTypePermission === 'DEFAULT_ONLY') { + return Object.keys(DefaultResourceType).find((key) => key === resourceType); + } else return false; + } } // curried function to verify a role in an action diff --git a/AMW_angular/io/src/app/auth/defaultResourceType.ts b/AMW_angular/io/src/app/auth/defaultResourceType.ts new file mode 100644 index 000000000..703cf24b4 --- /dev/null +++ b/AMW_angular/io/src/app/auth/defaultResourceType.ts @@ -0,0 +1,6 @@ +export enum DefaultResourceType { + APPLICATIONSERVER = 'Applicationserver', + APPLICATION = 'Application', + NODE = 'Node', + RUNTIME = 'Runtime', +} diff --git a/AMW_angular/io/src/app/deployment/deployment.component.ts b/AMW_angular/io/src/app/deployment/deployment.component.ts index e074ef0fa..fce41a0e0 100644 --- a/AMW_angular/io/src/app/deployment/deployment.component.ts +++ b/AMW_angular/io/src/app/deployment/deployment.component.ts @@ -25,7 +25,7 @@ import { LoadingIndicatorComponent } from '../shared/elements/loading-indicator. import { PageComponent } from '../layout/page/page.component'; @Component({ - selector: 'amw-deployment', + selector: 'app-deployment', templateUrl: './deployment.component.html', standalone: true, imports: [ diff --git a/AMW_angular/io/src/app/deployment/deployment.service.spec.ts b/AMW_angular/io/src/app/deployment/deployment.service.spec.ts index 51f717b6b..d04712f7e 100644 --- a/AMW_angular/io/src/app/deployment/deployment.service.spec.ts +++ b/AMW_angular/io/src/app/deployment/deployment.service.spec.ts @@ -5,7 +5,6 @@ import { Deployment } from './deployment'; import { DeploymentService } from './deployment.service'; describe('DeploymentService', () => { - let httpClient: HttpClient; let httpTestingController: HttpTestingController; let service: DeploymentService; @@ -16,7 +15,6 @@ describe('DeploymentService', () => { }); httpTestingController = TestBed.inject(HttpTestingController); - httpClient = TestBed.inject(HttpClient); service = TestBed.inject(DeploymentService); }); diff --git a/AMW_angular/io/src/app/deployment/environment.service.spec.ts b/AMW_angular/io/src/app/deployment/environment.service.spec.ts index 807c5923d..d4489d1ce 100644 --- a/AMW_angular/io/src/app/deployment/environment.service.spec.ts +++ b/AMW_angular/io/src/app/deployment/environment.service.spec.ts @@ -5,7 +5,6 @@ import { Environment } from './environment'; import { EnvironmentService } from './environment.service'; describe('DeploymentService', () => { - let httpClient: HttpClient; let httpTestingController: HttpTestingController; let service: EnvironmentService; @@ -20,12 +19,11 @@ describe('DeploymentService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [], - providers: [EnvironmentService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}); + imports: [], + providers: [EnvironmentService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }); httpTestingController = TestBed.inject(HttpTestingController); - httpClient = TestBed.inject(HttpClient); service = TestBed.inject(EnvironmentService); }); diff --git a/AMW_angular/io/src/app/deployments/deployment-container/deployment-container.component.ts b/AMW_angular/io/src/app/deployments/deployment-container/deployment-container.component.ts index 929afef9b..40084e819 100644 --- a/AMW_angular/io/src/app/deployments/deployment-container/deployment-container.component.ts +++ b/AMW_angular/io/src/app/deployments/deployment-container/deployment-container.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { EnvironmentService } from '../../deployment/environment.service'; import { DeploymentService } from '../../deployment/deployment.service'; @@ -12,8 +12,6 @@ import { DeploymentService } from '../../deployment/deployment.service'; imports: [RouterOutlet], }) -export class DeploymentContainerComponent implements OnInit { +export class DeploymentContainerComponent { constructor() {} - - ngOnInit(): void {} } diff --git a/AMW_angular/io/src/app/deployments/deployments-edit-modal.component.ts b/AMW_angular/io/src/app/deployments/deployments-edit-modal.component.ts index 4eff497fc..1f66ec7ba 100644 --- a/AMW_angular/io/src/app/deployments/deployments-edit-modal.component.ts +++ b/AMW_angular/io/src/app/deployments/deployments-edit-modal.component.ts @@ -6,7 +6,7 @@ import { DateTimePickerComponent } from '../shared/date-time-picker/date-time-pi import { FormsModule } from '@angular/forms'; @Component({ - selector: 'amw-deployments-edit-modal', + selector: 'app-deployments-edit-modal', templateUrl: './deployments-edit-modal.component.html', standalone: true, imports: [FormsModule, DateTimePickerComponent], diff --git a/AMW_angular/io/src/app/deployments/deployments-list.component.ts b/AMW_angular/io/src/app/deployments/deployments-list.component.ts index 13f29cb8c..952dd4b1d 100644 --- a/AMW_angular/io/src/app/deployments/deployments-list.component.ts +++ b/AMW_angular/io/src/app/deployments/deployments-list.component.ts @@ -14,7 +14,7 @@ import { SortableIconComponent } from '../shared/sortable-icon/sortable-icon.com import { DatePipe } from '@angular/common'; @Component({ - selector: 'amw-deployments-list', + selector: 'app-deployments-list', templateUrl: './deployments-list.component.html', standalone: true, imports: [SortableIconComponent, FormsModule, IconComponent, RouterLink, DateTimePickerComponent, DatePipe], @@ -63,12 +63,12 @@ export class DeploymentsListComponent { this.deployment = _.find(this.deployments, ['id', deploymentId]); this.deploymentDate = DateTimeModel.fromEpoch(this.deployment.deploymentDate); this.modalService.open(content).result.then( - (result) => { + () => { this.deployment.deploymentDate = this.deploymentDate.toEpoch(); this.editDeploymentDate.emit(this.deployment); delete this.deploymentDate; }, - (reason) => { + () => { delete this.deploymentDate; }, ); @@ -78,30 +78,30 @@ export class DeploymentsListComponent { this.deployment = _.find(this.deployments, ['id', deploymentId]); this.modalService.open(content).result.then( - (result) => { + () => { this.doConfirmDeployment.emit(this.deployment); }, - (reason) => {}, + () => {}, ); } showReject(content, deploymentId: number) { this.deployment = _.find(this.deployments, ['id', deploymentId]); this.modalService.open(content).result.then( - (result) => { + () => { this.doRejectDeployment.emit(this.deployment); }, - (reason) => {}, + () => {}, ); } showCancel(content, deploymentId: number) { this.deployment = _.find(this.deployments, ['id', deploymentId]); this.modalService.open(content).result.then( - (result) => { + () => { this.doCancelDeployment.emit(this.deployment); }, - (reason) => {}, + () => {}, ); } diff --git a/AMW_angular/io/src/app/deployments/deployments.component.html b/AMW_angular/io/src/app/deployments/deployments.component.html index 7fd3f8dc5..1c147a052 100644 --- a/AMW_angular/io/src/app/deployments/deployments.component.html +++ b/AMW_angular/io/src/app/deployments/deployments.component.html @@ -42,7 +42,7 @@ +
- +
- + @if (!isValidRegex()) { -
Invalid regular expression pattern.
+
Invalid regular expression pattern.
}
- +
-
@@ -41,10 +47,10 @@
@for (item of propertyType.propertyTags; track item.name) {
- - {{item.name}} - - + + {{ item.name }} + +
}
@@ -54,8 +60,20 @@
diff --git a/AMW_angular/io/src/app/settings/property-types/property-type-edit.component.ts b/AMW_angular/io/src/app/settings/property-types/property-type-edit.component.ts index 5aa6f99dd..0feff0ef3 100644 --- a/AMW_angular/io/src/app/settings/property-types/property-type-edit.component.ts +++ b/AMW_angular/io/src/app/settings/property-types/property-type-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -8,12 +8,12 @@ import { IconComponent } from '../../shared/icon/icon.component'; import { PropertyTag } from './property-tag'; @Component({ - selector: 'amw-property-type-edit', + selector: 'app-property-type-edit', templateUrl: './property-type-edit.component.html', standalone: true, imports: [DatePickerComponent, IconComponent, FormsModule], }) -export class PropertyTypeEditComponent implements OnInit { +export class PropertyTypeEditComponent { @Input() propertyType: PropertyType; @Output() savePropertyType: EventEmitter = new EventEmitter(); @@ -24,8 +24,6 @@ export class PropertyTypeEditComponent implements OnInit { this.activeModal = activeModal; } - ngOnInit(): void {} - getTitle(): string { return this.propertyType.id ? `Edit ${this.title}` : `Add ${this.title}`; } @@ -69,7 +67,7 @@ export class PropertyTypeEditComponent implements OnInit { } addTag() { - let tag = this.newTag.trim(); + const tag = this.newTag.trim(); if (tag !== '') { this.propertyType.propertyTags.push({ name: tag, type: 'LOCAL' }); } diff --git a/AMW_angular/io/src/app/settings/property-types/property-types.component.html b/AMW_angular/io/src/app/settings/property-types/property-types.component.html index bd4a5d301..26cec3728 100644 --- a/AMW_angular/io/src/app/settings/property-types/property-types.component.html +++ b/AMW_angular/io/src/app/settings/property-types/property-types.component.html @@ -21,50 +21,57 @@

Property Types

- - - - - - - - + + + + + + + + - @for (property of propertyTypes(); track property.id) { - - - @if (canDisplay()) { + @for (property of propertyTypes(); track property.id) { + + + @if (canDisplay()) { + } + } - - }
Property NameEncryptedValidationTagsEditDelete
Property NameEncryptedValidationTagsEditDelete
{{ property.name }}
{{ property.name }}{{ property.encrypted ? 'Yes' : 'No' }} {{ property.validationRegex }}
@for (tag of property.propertyTags; track tag.name) { - {{tag.name}} + {{ tag.name }} }
@if (canEditName()) { - } @if (canDelete()) { - }
- diff --git a/AMW_angular/io/src/app/settings/property-types/property-types.component.spec.ts b/AMW_angular/io/src/app/settings/property-types/property-types.component.spec.ts index 574dcfd48..c0a949cc2 100644 --- a/AMW_angular/io/src/app/settings/property-types/property-types.component.spec.ts +++ b/AMW_angular/io/src/app/settings/property-types/property-types.component.spec.ts @@ -1,14 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PropertyTypesComponent } from './property-types.component'; -import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; describe('PropertyTypesComponent', () => { let component: PropertyTypesComponent; let fixture: ComponentFixture; - let httpTestingController: HttpTestingController; - let httpClient: HttpClient; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -19,8 +17,6 @@ describe('PropertyTypesComponent', () => { fixture = TestBed.createComponent(PropertyTypesComponent); component = fixture.componentInstance; fixture.detectChanges(); - httpTestingController = TestBed.inject(HttpTestingController); - httpClient = TestBed.inject(HttpClient); }); it('should create', () => { diff --git a/AMW_angular/io/src/app/settings/property-types/property-types.component.ts b/AMW_angular/io/src/app/settings/property-types/property-types.component.ts index 7dff69db6..df7483775 100644 --- a/AMW_angular/io/src/app/settings/property-types/property-types.component.ts +++ b/AMW_angular/io/src/app/settings/property-types/property-types.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, Signal, signal } from '@angular/core'; +import { Component, computed, inject, OnDestroy, OnInit, Signal, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AuthService } from '../../auth/auth.service'; import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component'; @@ -18,7 +18,7 @@ import { PropertyTypeDeleteComponent } from './property-type-delete.component'; imports: [CommonModule, IconComponent, LoadingIndicatorComponent], templateUrl: './property-types.component.html', }) -export class PropertyTypesComponent { +export class PropertyTypesComponent implements OnInit, OnDestroy { private authService = inject(AuthService); private propertyTypeService = inject(PropertyTypesService); private modalService = inject(NgbModal); @@ -104,7 +104,7 @@ export class PropertyTypesComponent { .save(propertyType) .pipe(takeUntil(this.destroy$)) .subscribe({ - next: (r) => this.toastService.success(`${this.PROPERTY_TYPE} saved.`), + next: () => this.toastService.success(`${this.PROPERTY_TYPE} saved.`), error: (e) => this.error.set(e), complete: () => { this.propertyTypeService.reload(); @@ -120,7 +120,7 @@ export class PropertyTypesComponent { .delete(propertyType.id) .pipe(takeUntil(this.destroy$)) .subscribe({ - next: (r) => this.toastService.success(`${this.PROPERTY_TYPE} deleted.`), + next: () => this.toastService.success(`${this.PROPERTY_TYPE} deleted.`), error: (e) => this.error.set(e), complete: () => { this.propertyTypeService.reload(); diff --git a/AMW_angular/io/src/app/settings/releases/release-delete.component.ts b/AMW_angular/io/src/app/settings/releases/release-delete.component.ts index 97c4a3419..cd3729a29 100644 --- a/AMW_angular/io/src/app/settings/releases/release-delete.component.ts +++ b/AMW_angular/io/src/app/settings/releases/release-delete.component.ts @@ -3,10 +3,10 @@ import { FormsModule } from '@angular/forms'; import { Release } from './release'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { AsyncPipe, KeyValuePipe } from '@angular/common'; -import { ResourceEntity } from './resourceEntity'; +import { ResourceEntity } from './resource-entity'; @Component({ - selector: 'amw-release-delete', + selector: 'app-release-delete', standalone: true, imports: [AsyncPipe, KeyValuePipe, FormsModule], templateUrl: './release-delete.component.html', diff --git a/AMW_angular/io/src/app/settings/releases/release-edit.component.ts b/AMW_angular/io/src/app/settings/releases/release-edit.component.ts index 9c456357b..3aa59af44 100644 --- a/AMW_angular/io/src/app/settings/releases/release-edit.component.ts +++ b/AMW_angular/io/src/app/settings/releases/release-edit.component.ts @@ -7,7 +7,7 @@ import { DATE_FORMAT } from '../../core/amw-constants'; import { DateModel } from '../../shared/date-picker/date.model'; @Component({ - selector: 'amw-release-edit', + selector: 'app-release-edit', templateUrl: './release-edit.component.html', standalone: true, imports: [DatePickerComponent, FormsModule], diff --git a/AMW_angular/io/src/app/settings/releases/releases.component.html b/AMW_angular/io/src/app/settings/releases/releases.component.html index 8c58d41cc..00d4756f0 100644 --- a/AMW_angular/io/src/app/settings/releases/releases.component.html +++ b/AMW_angular/io/src/app/settings/releases/releases.component.html @@ -40,14 +40,14 @@

Releases

{{ item.description }} {{ item.installationInProductionAt | date: dateFormat }} - @if (item.default != true && canEdit()) { + @if (item.default !== true && canEdit()) { } - @if (item.default != true && canDelete()) { + @if (item.default !== true && canDelete()) { @@ -62,13 +62,13 @@

Releases

diff --git a/AMW_angular/io/src/app/settings/releases/releases.component.spec.ts b/AMW_angular/io/src/app/settings/releases/releases.component.spec.ts index ce0284079..82acead56 100644 --- a/AMW_angular/io/src/app/settings/releases/releases.component.spec.ts +++ b/AMW_angular/io/src/app/settings/releases/releases.component.spec.ts @@ -1,26 +1,22 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReleasesComponent } from './releases.component'; -import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; -import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; describe('ReleasesComponent', () => { let component: ReleasesComponent; let fixture: ComponentFixture; - let httpTestingController: HttpTestingController; - let httpClient: HttpClient; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ReleasesComponent], - providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}).compileComponents(); + imports: [ReleasesComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); fixture = TestBed.createComponent(ReleasesComponent); component = fixture.componentInstance; fixture.detectChanges(); - httpTestingController = TestBed.inject(HttpTestingController); - httpClient = TestBed.inject(HttpClient); }); it('should create', () => { diff --git a/AMW_angular/io/src/app/settings/releases/releases.component.ts b/AMW_angular/io/src/app/settings/releases/releases.component.ts index 0863e16d4..fa2eae631 100644 --- a/AMW_angular/io/src/app/settings/releases/releases.component.ts +++ b/AMW_angular/io/src/app/settings/releases/releases.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit, signal } from '@angular/core'; +import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core'; import { AsyncPipe, DatePipe } from '@angular/common'; import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; @@ -28,7 +28,7 @@ import { ToastService } from '../../shared/elements/toast/toast.service'; ], templateUrl: './releases.component.html', }) -export class ReleasesComponent implements OnInit { +export class ReleasesComponent implements OnInit, OnDestroy { private authService = inject(AuthService); private modalService = inject(NgbModal); private releasesService = inject(ReleasesService); @@ -130,7 +130,7 @@ export class ReleasesComponent implements OnInit { .save(release) .pipe(takeUntil(this.destroy$)) .subscribe({ - next: (r) => this.toastService.success('Release saved successfully.'), + next: () => this.toastService.success('Release saved successfully.'), error: (e) => this.error$.next(e), complete: () => this.getReleases(), }); @@ -157,7 +157,7 @@ export class ReleasesComponent implements OnInit { .delete(release.id) .pipe(takeUntil(this.destroy$)) .subscribe({ - next: (r) => this.toastService.success('Release deleted.'), + next: () => this.toastService.success('Release deleted.'), error: (e) => this.error$.next(e), complete: () => this.getReleases(), }); diff --git a/AMW_angular/io/src/app/settings/releases/releases.service.ts b/AMW_angular/io/src/app/settings/releases/releases.service.ts index f20ffecbd..1df366234 100644 --- a/AMW_angular/io/src/app/settings/releases/releases.service.ts +++ b/AMW_angular/io/src/app/settings/releases/releases.service.ts @@ -4,7 +4,7 @@ import { HttpClient } from '@angular/common/http'; import { map, catchError } from 'rxjs/operators'; import { Release } from './release'; import { Observable } from 'rxjs'; -import { ResourceEntity } from './resourceEntity'; +import { ResourceEntity } from './resource-entity'; @Injectable({ providedIn: 'root' }) export class ReleasesService extends BaseService { @@ -29,8 +29,8 @@ export class ReleasesService extends BaseService { getReleaseResources(id: number): Observable> { return this.http.get(`${this.getBaseUrl()}/releases/${id}/resources`).pipe( map((jsonObject) => { - let resourceMap = new Map(); - for (var value in jsonObject) { + const resourceMap = new Map(); + for (const value in jsonObject) { resourceMap.set(value, jsonObject[value]); } return resourceMap; diff --git a/AMW_angular/io/src/app/settings/releases/resourceEntity.ts b/AMW_angular/io/src/app/settings/releases/resource-entity.ts similarity index 100% rename from AMW_angular/io/src/app/settings/releases/resourceEntity.ts rename to AMW_angular/io/src/app/settings/releases/resource-entity.ts diff --git a/AMW_angular/io/src/app/settings/settings.component.html b/AMW_angular/io/src/app/settings/settings.component.html index ab0b2319d..ab911aeea 100644 --- a/AMW_angular/io/src/app/settings/settings.component.html +++ b/AMW_angular/io/src/app/settings/settings.component.html @@ -1,6 +1,6 @@
Settings
- {{ loadingPermissions()}} + {{ loadingPermissions() }} @if (canViewSettings) {
@@ -11,13 +11,13 @@ Tags } Functions - Deployment - Parameter + Deployment Parameter Releases @if (canViewPermissionsTab) { Roles and Permissions - } - @if (canViewAppInfo) { + } @if (canViewAppInfo) { Application Info } @@ -26,5 +26,5 @@
-} + }
diff --git a/AMW_angular/io/src/app/settings/settings.component.spec.ts b/AMW_angular/io/src/app/settings/settings.component.spec.ts index 0826ca2e8..cfc86448a 100644 --- a/AMW_angular/io/src/app/settings/settings.component.spec.ts +++ b/AMW_angular/io/src/app/settings/settings.component.spec.ts @@ -8,20 +8,16 @@ import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/ describe('SettingsComponent', () => { let component: SettingsComponent; let fixture: ComponentFixture; - let httpTestingController: HttpTestingController; - let httpClient: HttpClient; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SettingsComponent, RouterTestingModule], - providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] + imports: [SettingsComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(SettingsComponent); component = fixture.componentInstance; fixture.detectChanges(); - httpTestingController = TestBed.inject(HttpTestingController); - httpClient = TestBed.inject(HttpClient); }); it('should create', () => { diff --git a/AMW_angular/io/src/app/settings/tags/tags.component.scss b/AMW_angular/io/src/app/settings/tags/tags.component.scss index af303c15d..e7409aad0 100644 --- a/AMW_angular/io/src/app/settings/tags/tags.component.scss +++ b/AMW_angular/io/src/app/settings/tags/tags.component.scss @@ -1,3 +1,4 @@ -.table td, .table th { +.table td, +.table th { vertical-align: middle; } diff --git a/AMW_angular/io/src/app/shared/elements/toast/toast-container.component.spec.ts b/AMW_angular/io/src/app/shared/elements/toast/toast-container.component.spec.ts index 97eafb6c5..7e8bb9cc5 100644 --- a/AMW_angular/io/src/app/shared/elements/toast/toast-container.component.spec.ts +++ b/AMW_angular/io/src/app/shared/elements/toast/toast-container.component.spec.ts @@ -1,19 +1,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ToastContainerComponent } from './toast-container.component'; -import { ToastService } from './toast.service'; describe('ToastContainerComponent', () => { let component: ToastContainerComponent; - let toastService: ToastService; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ - providers: [ToastContainerComponent, { provide: ToastService, useClass: ToastService }], + providers: [ToastContainerComponent], }); fixture = TestBed.createComponent(ToastContainerComponent); component = TestBed.inject(ToastContainerComponent); - toastService = TestBed.inject(ToastService); }); it('should create the component', () => { diff --git a/AMW_angular/io/src/app/shared/interceptors/http-toast.interceptor.ts b/AMW_angular/io/src/app/shared/interceptors/http-toast.interceptor.ts index 84fcf4157..b248183ec 100644 --- a/AMW_angular/io/src/app/shared/interceptors/http-toast.interceptor.ts +++ b/AMW_angular/io/src/app/shared/interceptors/http-toast.interceptor.ts @@ -12,7 +12,6 @@ export function provideHttpToastInterceptor(): Provider[] { }) export class HttpToastInterceptor implements HttpInterceptor { toastService = inject(ToastService); - intercept(req: HttpRequest, next: HttpHandler) { return next.handle(req).pipe( catchError((error) => { diff --git a/AMW_angular/io/src/app/shared/pagination/pagination.component.html b/AMW_angular/io/src/app/shared/pagination/pagination.component.html index 9aeced220..17cb1a76e 100644 --- a/AMW_angular/io/src/app/shared/pagination/pagination.component.html +++ b/AMW_angular/io/src/app/shared/pagination/pagination.component.html @@ -16,7 +16,7 @@ @for (page of pages(); track page) {
  • {{ page }} @if (currentPage === page) { + >{{ page }} @if (currentPage === page) { (current) } diff --git a/AMW_angular/io/src/app/shared/pagination/pagination.component.ts b/AMW_angular/io/src/app/shared/pagination/pagination.component.ts index c368194a7..088337a9a 100644 --- a/AMW_angular/io/src/app/shared/pagination/pagination.component.ts +++ b/AMW_angular/io/src/app/shared/pagination/pagination.component.ts @@ -2,7 +2,7 @@ import { Component, Input, EventEmitter, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; @Component({ - selector: 'amw-pagination', + selector: 'app-pagination', templateUrl: './pagination.component.html', standalone: true, imports: [FormsModule], diff --git a/AMW_angular/pom.xml b/AMW_angular/pom.xml index 59e584d46..7a1f3250d 100644 --- a/AMW_angular/pom.xml +++ b/AMW_angular/pom.xml @@ -44,7 +44,7 @@ 1.13.3 v20.11.1 - 10.4.0 + 10.8.2 io ${user.home}/.node @@ -63,17 +63,17 @@ npm - ci + install - npm run-script mavenbuild + npm run mavenbuild compile npm - run-script mavenbuild + run mavenbuild diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppCommand.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppCommand.java new file mode 100644 index 000000000..d93ebb534 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppCommand.java @@ -0,0 +1,26 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.business.apps.validation.ValidAppName; +import lombok.Getter; + +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; + +import static ch.puzzle.itc.mobiliar.business.utils.Validation.validate; + +@Getter +public class AddAppCommand { + + @ValidAppName + private final String appName; + + @NotNull + private final Integer releaseId; + + public AddAppCommand(String appName, Integer releaseId) throws ValidationException { + this.appName = appName; + this.releaseId = releaseId; + validate(this); + } + +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppServerUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppServerUseCase.java new file mode 100644 index 000000000..0103c6ac5 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppServerUseCase.java @@ -0,0 +1,7 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; + +public interface AddAppServerUseCase { + Integer add(AppServerCommand appServerCommand) throws NotFoundException, IllegalStateException; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppUseCase.java new file mode 100644 index 000000000..4441f911b --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppUseCase.java @@ -0,0 +1,8 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.common.exception.*; + + +public interface AddAppUseCase { + Integer add(AddAppCommand addAppCommand) throws NotFoundException, IllegalStateException; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerCommand.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerCommand.java new file mode 100644 index 000000000..07a17b5e1 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerCommand.java @@ -0,0 +1,30 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.business.apps.validation.ValidAppName; +import lombok.Getter; + +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; + +import static ch.puzzle.itc.mobiliar.business.utils.Validation.validate; + +@Getter +public class AddAppWithServerCommand { + + @ValidAppName + private final String appName; + @NotNull + private final Integer releaseId; + @NotNull + private final Integer appServerId; + @NotNull + private final Integer appServerReleaseId; + + public AddAppWithServerCommand(String appName, Integer releaseId, Integer appServerId, Integer appServerReleaseId) throws ValidationException { + this.appName = appName; + this.releaseId = releaseId; + this.appServerId = appServerId; + this.appServerReleaseId = appServerReleaseId; + validate(this); + } +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerUseCase.java new file mode 100644 index 000000000..0d8873723 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AddAppWithServerUseCase.java @@ -0,0 +1,8 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; + + +public interface AddAppWithServerUseCase { + Integer add(AddAppWithServerCommand addAppWithServerCommand) throws NotFoundException, IllegalStateException; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AppServerCommand.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AppServerCommand.java new file mode 100644 index 000000000..6ac9879d9 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/AppServerCommand.java @@ -0,0 +1,24 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.business.apps.validation.ValidAppName; +import lombok.Getter; + +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; + +import static ch.puzzle.itc.mobiliar.business.utils.Validation.validate; + +@Getter +public class AppServerCommand { + @ValidAppName + private final String appServerName; + + @NotNull + private final Integer releaseId; + + public AppServerCommand(String serverName, Integer releaseId) throws ValidationException { + this.appServerName = serverName; + this.releaseId = releaseId; + validate(this); + } +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/ListAppsUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/ListAppsUseCase.java new file mode 100644 index 000000000..4ea02cdb8 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/boundary/ListAppsUseCase.java @@ -0,0 +1,13 @@ +package ch.puzzle.itc.mobiliar.business.apps.boundary; + +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; +import ch.puzzle.itc.mobiliar.common.util.Tuple; + +import java.util.List; + +public interface ListAppsUseCase { + + Tuple, Long> appsFor(Integer startIndex, Integer maxResults, String filter, Integer releaseId) throws NotFoundException; + +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/control/AppsService.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/control/AppsService.java new file mode 100644 index 000000000..8f091a04b --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/control/AppsService.java @@ -0,0 +1,121 @@ +package ch.puzzle.itc.mobiliar.business.apps.control; + +import ch.puzzle.itc.mobiliar.business.apps.boundary.*; +import ch.puzzle.itc.mobiliar.business.foreignable.entity.ForeignableOwner; +import ch.puzzle.itc.mobiliar.business.releasing.boundary.ReleaseLocator; +import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; +import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceBoundary; +import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceRelations; +import ch.puzzle.itc.mobiliar.business.resourcegroup.control.ResourceTypeRepository; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.Application; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.Resource; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceTypeEntity; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; +import ch.puzzle.itc.mobiliar.business.security.boundary.PermissionBoundary; +import ch.puzzle.itc.mobiliar.business.security.entity.Permission; +import ch.puzzle.itc.mobiliar.business.security.interceptor.HasPermission; +import ch.puzzle.itc.mobiliar.common.exception.AMWException; +import ch.puzzle.itc.mobiliar.common.exception.ElementAlreadyExistsException; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; +import ch.puzzle.itc.mobiliar.common.exception.ResourceTypeNotFoundException; +import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; +import ch.puzzle.itc.mobiliar.common.util.Tuple; + +import javax.ejb.Stateless; +import javax.inject.Inject; +import java.util.List; + +import static ch.puzzle.itc.mobiliar.business.security.entity.Action.CREATE; + +@Stateless +public class AppsService implements ListAppsUseCase, AddAppServerUseCase, AddAppUseCase, AddAppWithServerUseCase { + + @Inject + private ReleaseLocator releaseLocator; + + @Inject + private ResourceRelations resourceRelations; + + @Inject + private ResourceBoundary resourceBoundary; + + @Inject + private PermissionBoundary permissionBoundary; + + @Inject + private ResourceTypeRepository resourceTypeRepository; + + @Override + public Tuple, Long> appsFor(Integer startIndex, Integer maxResults, String filter, Integer releaseId) throws NotFoundException { + ReleaseEntity release = releaseLocator.getReleaseById(releaseId); + return resourceRelations.getAppServersWithApplications(startIndex, maxResults, filter, release); + } + + + @Override + @HasPermission(permission = Permission.RESOURCE, action = CREATE) + public Integer add(AddAppCommand command) throws NotFoundException, IllegalStateException { + + try { + Application app = resourceBoundary.createNewApplicationWithoutAppServerByName( + ForeignableOwner.getSystemOwner(), null, null, command.getAppName(), command.getReleaseId(), false); + permissionBoundary.createAutoAssignedRestrictions(app.getEntity()); + + return app.getId(); + + } catch (ElementAlreadyExistsException e) { + throw new IllegalStateException(e.getMessage()); + } catch (ResourceTypeNotFoundException e) { + throw new IllegalStateException("A resource with group name \"" + command.getAppName() + "\" already exist and can not be created!"); + } catch (AMWException e) { + throw new IllegalStateException("Failed to create auto assigned restrictions for app: " + command.getAppName(), e); + } + } + + @Override + @HasPermission(permission = Permission.RESOURCE, action = CREATE) + public Integer add(AddAppWithServerCommand command) throws NotFoundException, IllegalStateException { + try { + Application app = resourceBoundary.createNewUniqueApplicationForAppServer( + ForeignableOwner.getSystemOwner(), + command.getAppName(), + command.getAppServerId(), + command.getReleaseId(), + command.getAppServerReleaseId()); + permissionBoundary.createAutoAssignedRestrictions(app.getEntity()); + + return app.getId(); + + } catch (ElementAlreadyExistsException e) { + String type = e.getExistingObjectClass() == Application.class ? "application" : "application server"; + throw new IllegalStateException("An " + type + " with the name " + e.getExistingObjectName() + + " already exists."); + } catch (ResourceTypeNotFoundException e) { + throw new IllegalStateException("A resource with group name \"" + command.getAppName() + "\" already exist and can not be created!"); + } catch (AMWException e) { + throw new IllegalStateException("Failed to create auto assigned restrictions for app: " + command.getAppName(), e); + } + } + + + @Override + @HasPermission(permission = Permission.RESOURCE, action = CREATE) + public Integer add(AppServerCommand command) throws NotFoundException, IllegalStateException { + ResourceTypeEntity resourceType = resourceTypeRepository.getByName(String.valueOf(DefaultResourceTypeDefinition.APPLICATIONSERVER)); + ReleaseEntity release = releaseLocator.getReleaseById(command.getReleaseId()); + try { + Resource resource = resourceBoundary.createNewResourceByName(ForeignableOwner.getSystemOwner(), command.getAppServerName(), + resourceType.getId(), release.getId()); + permissionBoundary.createAutoAssignedRestrictions(resource.getEntity()); + + return resource.getId(); + + } catch (ElementAlreadyExistsException | ResourceTypeNotFoundException e) { + throw new IllegalStateException(e.getMessage()); + } catch (AMWException e) { + throw new IllegalStateException("Failed to create auto assigned restrictions for app: " + command.getAppServerName(), e); + } + + } +} + diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppName.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppName.java new file mode 100644 index 000000000..498bf3cd7 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppName.java @@ -0,0 +1,23 @@ +package ch.puzzle.itc.mobiliar.business.apps.validation; + + +import javax.validation.Constraint; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ FIELD }) +@Retention(RUNTIME) +@Constraint(validatedBy = ValidAppNameValidator.class) +@Documented +public @interface ValidAppName { + + String message() default "The name contains empty space or dots"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppNameValidator.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppNameValidator.java new file mode 100644 index 000000000..ff0ee8eea --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/apps/validation/ValidAppNameValidator.java @@ -0,0 +1,15 @@ +package ch.puzzle.itc.mobiliar.business.apps.validation; + +import ch.puzzle.itc.mobiliar.common.util.NameChecker; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class ValidAppNameValidator implements ConstraintValidator { + + @Override + public boolean isValid(String appName, ConstraintValidatorContext constraintValidatorContext) { + if (appName == null) return false; + return NameChecker.isNameValid(appName); + } +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainService.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainService.java index 40a8195a9..3ee621af7 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainService.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainService.java @@ -27,6 +27,7 @@ import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; import ch.puzzle.itc.mobiliar.common.util.ApplicationServerContainer; +import ch.puzzle.itc.mobiliar.common.util.Tuple; /** * The ScreenDomainService for Applist Screens @@ -37,22 +38,24 @@ public class ApplistScreenDomainService { @Inject private ApplistScreenDomainServiceQueries queries; - List getApplicationServerResources(String filter, Integer maxResults) { - return queries.doFetchApplicationServersWithApplicationsOrderedByAppServerNameCaseInsensitive(filter, maxResults); + Tuple, Long> getApplicationServerResources(Integer startIndex, Integer maxResults, String filter) { + return queries.getAppServersWithApps(startIndex, maxResults, filter); } - public List getAppServerResourcesWithApplications(String filter, Integer maxResults, boolean withAppServerContainer) { - List appServerList = getApplicationServerResources(filter, maxResults); + public Tuple, Long> getAppServerResourcesWithApplications(Integer startIndex, Integer maxResults, String filter, boolean withAppServerContainer) { + Tuple, Long> result = getApplicationServerResources(startIndex, maxResults, filter); + List appServerList = result.getA(); for (ResourceEntity as : appServerList) { if (as.getName().equals(ApplicationServerContainer.APPSERVERCONTAINER.getDisplayName())) { if (!withAppServerContainer || as.getConsumedMasterRelations().isEmpty()) { appServerList.remove(as); break; - }; + } + ; } } - return appServerList; + return new Tuple, Long>(appServerList, result.getB()); } } diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceQueries.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceQueries.java index 9ecf83949..8575287c3 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceQueries.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceQueries.java @@ -33,6 +33,7 @@ import ch.puzzle.itc.mobiliar.business.utils.JpaWildcardConverter; import ch.puzzle.itc.mobiliar.business.utils.database.DatabaseUtil; import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; +import ch.puzzle.itc.mobiliar.common.util.Tuple; public class ApplistScreenDomainServiceQueries { @@ -42,14 +43,20 @@ public class ApplistScreenDomainServiceQueries { @Inject private DatabaseUtil dbUtil; - List doFetchApplicationServersWithApplicationsOrderedByAppServerNameCaseInsensitive(String nameFilter, Integer maxResult) { + + Tuple, Long> getAppServersWithApps(Integer startIndex, Integer maxResult, String nameFilter) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); Predicate p; boolean nameFilterIsEmpty = nameFilter == null || nameFilter.trim().isEmpty(); + + + // Count all values before filtering + Long totalCount = getTotalCount(cb); + + // Filter and retrieve results CriteriaQuery q = cb.createQuery(ResourceEntity.class); Root appServer = q.from(ResourceEntity.class); - Join appServerType = appServer.join("resourceType", JoinType.LEFT); SetJoin relation = appServer.joinSet("consumedMasterRelations", JoinType.LEFT); Join app = relation.join("slaveResource", JoinType.LEFT); @@ -75,12 +82,30 @@ List doFetchApplicationServersWithApplicationsOrderedByAppServer } q.orderBy(cb.asc(appServer.get("deletable")), cb.asc(name)); + TypedQuery query = entityManager.createQuery(q); + + if (startIndex != null) { + query.setFirstResult(startIndex); + } + if (maxResult != null) { query.setMaxResults(maxResult); - } + } + + return new Tuple<>(query.getResultList(), totalCount); + } - return query.getResultList(); + private Long getTotalCount(CriteriaBuilder cb) { + CriteriaQuery countQuery = cb.createQuery(Long.class); + Root appServer = countQuery.from(ResourceEntity.class); + Join appServerType = appServer.join("resourceType", JoinType.LEFT); + SetJoin relation = appServer.joinSet("consumedMasterRelations", JoinType.LEFT); + Join app = relation.join("slaveResource", JoinType.LEFT); + countQuery.select(cb.countDistinct(appServer.get("id"))); + countQuery.where(cb.equal(appServerType.get("name"), DefaultResourceTypeDefinition.APPLICATIONSERVER.name())); + Long totalCount = entityManager.createQuery(countQuery).getSingleResult(); + return totalCount; } -} \ No newline at end of file +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceBoundary.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceBoundary.java index 6d8dc5f9d..4510417d2 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceBoundary.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceBoundary.java @@ -100,7 +100,7 @@ public Resource createNewResourceByName(ForeignableOwner creatingOwner, String n ReleaseEntity release; try { release = releaseLocator.getReleaseByName(releaseName); - } catch (Exception e) { + } catch (Exception e) { String message = "Release '" + releaseName + "' doesn't exist"; log.info(message); throw new ResourceNotFoundException(message); @@ -120,7 +120,7 @@ public Resource createNewResourceByName(ForeignableOwner creatingOwner, String n private Resource createNewResourceByName(ForeignableOwner creatingOwner, String newResourceName, ResourceTypeEntity resourceTypeEntity, Integer releaseId, boolean canCreateReleaseOfExisting) throws ElementAlreadyExistsException, ResourceTypeNotFoundException { - if (!permissionBoundary.canCreateResourceInstance(resourceTypeEntity)){ + if (!permissionBoundary.canCreateResourceInstance(resourceTypeEntity)) { throw new NotAuthorizedException("Permission Denied"); } ResourceEntity resourceEntity = createResourceEntityByNameForResourceType(creatingOwner, newResourceName, @@ -162,11 +162,11 @@ private ResourceEntity createResourceEntityByNameForResourceType(ForeignableOwne resourceEntity = ResourceFactory.createNewResourceForOwner(newResourceName, creatingOwner); log.info("Created new Resource " + newResourceName + " and group in Release " + release.getName()); } else { - String message = "A " + anotherGroup.getResourceType().getName()+" with the same name: " + newResourceName + " already exists."; + String message = "A " + anotherGroup.getResourceType().getName() + " with the same name: " + newResourceName + " already exists."; log.info(message); throw new ElementAlreadyExistsException(message, Resource.class, newResourceName); } - } else if (canCreateReleaseOfExisting) { + } else if (canCreateReleaseOfExisting) { // check if group contains resource for release for (ResourceEntity r : group.getResources()) { if (r.getRelease().getId().equals(releaseId)) { @@ -180,7 +180,7 @@ private ResourceEntity createResourceEntityByNameForResourceType(ForeignableOwne log.info("Created new Resource " + newResourceName + " for existing group in Release " + release.getName()); } else { // if resource with given name, type and release already exists throw an exeption - String message = "The "+type.getName()+" with name: " + newResourceName + " already exists in release "+release.getName(); + String message = "The " + type.getName() + " with name: " + newResourceName + " already exists in release " + release.getName(); log.info(message); throw new ElementAlreadyExistsException(message, Resource.class, newResourceName); } @@ -202,7 +202,7 @@ private ResourceEntity createResourceEntityByNameForResourceType(ForeignableOwne * @throws ResourceTypeNotFoundException * @throws ElementAlreadyExistsException */ - private Application createUniqueApplicationByName(ForeignableOwner creatingOwner,String applicationName, int releaseId, boolean canCreateReleaseOfExisting) + private Application createUniqueApplicationByName(ForeignableOwner creatingOwner, String applicationName, int releaseId, boolean canCreateReleaseOfExisting) throws ResourceTypeNotFoundException, ElementAlreadyExistsException { ResourceTypeEntity resourceTypeEntity = resourceTypeProvider.getOrCreateDefaultResourceType(DefaultResourceTypeDefinition.APPLICATION); ResourceEntity resourceEntity = createResourceEntityByNameForResourceType(creatingOwner, applicationName, @@ -225,7 +225,7 @@ public Application createNewUniqueApplicationForAppServer(ForeignableOwner creat ResourceEntity asResource = commonService.getResourceEntityByGroupAndRelease(asGroupId, asReleaseId); - if(!permissionBoundary.canCreateAppAndAddToAppServer(asResource)){ + if (!permissionBoundary.canCreateAppAndAddToAppServer(asResource)) { throw new NotAuthorizedException("Missing Permission"); } @@ -309,7 +309,7 @@ private void doRemoveResourceEntity(ForeignableOwner deletingOwner, Integer reso foreignableService.verifyDeletableByOwner(deletingOwner, resourceEntity); - if ( !permissionBoundary.hasPermission(Permission.RESOURCE, contextDomainService.getGlobalResourceContextEntity(), + if (!permissionBoundary.hasPermission(Permission.RESOURCE, contextDomainService.getGlobalResourceContextEntity(), Action.DELETE, resourceEntity, resourceEntity.getResourceType())) { throw new NotAuthorizedException(); } @@ -345,7 +345,7 @@ private void doRemoveResourceEntity(ForeignableOwner deletingOwner, Integer reso } } - private long countNumberOfConsumedSlaveRelations(ResourceEntity res){ + private long countNumberOfConsumedSlaveRelations(ResourceEntity res) { return entityManager.createQuery("select count(a.id) from ResourceEntity r left join r.consumedSlaveRelations a where r=:res", Long.class).setParameter("res", res).getSingleResult(); } @@ -381,8 +381,7 @@ private ResourceEntity getOrCreateResourceEntityByNameForResourceType(Foreignabl // if group does not exists a new resource with a new group can be created resourceEntity = ResourceFactory.createNewResourceForOwner(newResourceName, creatingOwner); log.info("Create new Resource " + newResourceName + " and group in Release " + release.getName()); - } - else { + } else { // check if group contains resource for this release for (ResourceEntity r : group.getResources()) { if (r.getRelease().getId().equals(releaseId)) { diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelations.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelations.java index 523d98485..55494b0eb 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelations.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelations.java @@ -26,10 +26,10 @@ import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceGroupEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; -import ch.puzzle.itc.mobiliar.business.security.control.PermissionService; -import ch.puzzle.itc.mobiliar.business.usersettings.control.UserSettingsService; -import ch.puzzle.itc.mobiliar.business.usersettings.entity.UserSettingsEntity; +import ch.puzzle.itc.mobiliar.business.security.entity.Permission; +import ch.puzzle.itc.mobiliar.business.security.interceptor.HasPermission; import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; +import ch.puzzle.itc.mobiliar.common.util.Tuple; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; @@ -45,17 +45,16 @@ public class ResourceRelations { @Inject ApplistScreenDomainService applistScreenDomainService; - @Inject - UserSettingsService userSettingsService; - @Inject - PermissionService permissionService; + @Inject ResourceDependencyResolverService dependencyResolverService; - public List getAppServersWithApplications(String filter, Integer maxResults, ReleaseEntity release) { - UserSettingsEntity userSettings = userSettingsService.getUserSettings(permissionService.getCurrentUserName()); - List appServersWithAllApplications = applistScreenDomainService.getAppServerResourcesWithApplications(filter, maxResults, true); - return filterAppServersByRelease(release, appServersWithAllApplications); + @HasPermission(permission = Permission.APP_AND_APPSERVER_LIST) + public Tuple, Long> getAppServersWithApplications(Integer startIndex, Integer maxResults, String filter, ReleaseEntity release) { + Tuple, Long> result = applistScreenDomainService.getAppServerResourcesWithApplications(startIndex, maxResults, filter, true); + List appServersWithAllApplications = result.getA(); + List filteredResult = filterAppServersByRelease(release, appServersWithAllApplications); + return new Tuple<>(filteredResult, result.getB()); } @TransactionAttribute(TransactionAttributeType.REQUIRED) diff --git a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceTest.java b/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceTest.java deleted file mode 100644 index 385d7f586..000000000 --- a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/domain/applist/ApplistScreenDomainServiceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * AMW - Automated Middleware allows you to manage the configurations of - * your Java EE applications on an unlimited number of different environments - * with various versions, including the automated deployment of those apps. - * Copyright (C) 2013-2016 by Puzzle ITC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ch.puzzle.itc.mobiliar.business.domain.applist; - -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -public class ApplistScreenDomainServiceTest { - - @Mock - ApplistScreenDomainServiceQueries applistScreenDomainServiceQueries; - - @InjectMocks - ApplistScreenDomainService applistScreenDomainService; - - @Before - public void before() { - MockitoAnnotations.openMocks(this); - } - - /** - * @throws Exception - */ - @Test - public void testGetApplicationServers() throws Exception { - List applicationServers = applistScreenDomainService - .getAppServerResourcesWithApplications("*", 42, true); - Assert.assertTrue(applicationServers.isEmpty()); - } - - /** - * @throws Exception - */ - @Test - public void testGetApplicationServerResources() throws Exception { - List applicationServers = applistScreenDomainService - .getApplicationServerResources("*", 42); - Assert.assertTrue(applicationServers.isEmpty()); - } -} diff --git a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsIntegrationTest.java b/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsIntegrationTest.java deleted file mode 100644 index 4b8c9db6c..000000000 --- a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsIntegrationTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * AMW - Automated Middleware allows you to manage the configurations of - * your Java EE applications on an unlimited number of different environments - * with various versions, including the automated deployment of those apps. - * Copyright (C) 2013-2016 by Puzzle ITC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ch.puzzle.itc.mobiliar.business.resourcegroup.boundary; - -import ch.puzzle.itc.mobiliar.business.domain.applist.ApplistScreenDomainService; -import ch.puzzle.itc.mobiliar.business.foreignable.entity.ForeignableOwner; -import ch.puzzle.itc.mobiliar.business.generator.control.extracted.ResourceDependencyResolverService; -import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.*; -import ch.puzzle.itc.mobiliar.business.resourcerelation.entity.ResourceRelationTypeEntity; -import ch.puzzle.itc.mobiliar.business.security.control.PermissionService; -import ch.puzzle.itc.mobiliar.business.usersettings.control.UserSettingsService; -import ch.puzzle.itc.mobiliar.business.usersettings.entity.UserSettingsEntity; -import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; -import ch.puzzle.itc.mobiliar.test.testrunner.PersistenceTestRunner; -import org.apache.commons.lang3.time.DateUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.util.Arrays; -import java.util.Date; - -@RunWith(PersistenceTestRunner.class) -public class ResourceRelationsIntegrationTest { - - @PersistenceContext - private EntityManager entityManager; - - @InjectMocks - ResourceRelations service; - - @Mock - UserSettingsService userSettingsService; - @Mock - PermissionService permissionService; - - @Mock - ApplistScreenDomainService applistScreenDomainService; - - @Mock - UserSettingsEntity userSettingsEntity; - - @InjectMocks - ResourceDependencyResolverService dependencyResolver; - - ReleaseEntity release1; - ReleaseEntity release2; - ResourceTypeEntity astype; - ResourceTypeEntity apptype; - ResourceEntity asRel1; - ResourceEntity asRel2; - ResourceEntity appRel1; - ResourceEntity appRel2; - - @Before - public void before() { - MockitoAnnotations.openMocks(this); - release1 = new ReleaseEntity(); - release1.setName("Release 1"); - release1.setInstallationInProductionAt(DateUtils.addDays(new Date(), -1)); - release2 = new ReleaseEntity(); - release2.setName("Release 2"); - release2.setInstallationInProductionAt(new Date()); - entityManager.persist(release1); - entityManager.persist(release2); - - astype = new ResourceTypeEntity(); - astype.setName(DefaultResourceTypeDefinition.APPLICATIONSERVER.name()); - apptype = new ResourceTypeEntity(); - apptype.setName(DefaultResourceTypeDefinition.APPLICATION.name()); - entityManager.persist(astype); - entityManager.persist(apptype); - - asRel1 = ResourceFactory.createNewResource("as"); - asRel1.setRelease(release1); - asRel2 = ResourceFactory.createNewResource(asRel1.getResourceGroup()); - asRel2.setRelease(release2); - entityManager.persist(asRel1); - entityManager.persist(asRel2); - - appRel1 = ResourceFactory.createNewResource("app"); - appRel1.setRelease(release1); - appRel2 = ResourceFactory.createNewResource(appRel1.getResourceGroup()); - appRel2.setRelease(release2); - entityManager.persist(appRel1); - entityManager.persist(appRel2); - - ResourceRelationTypeEntity resRelType = new ResourceRelationTypeEntity(); - resRelType.setResourceTypes(astype, apptype); - entityManager.persist(resRelType); - - entityManager.persist(asRel1.addConsumedResourceRelation(appRel1, resRelType, null, ForeignableOwner.AMW)); - entityManager.persist(asRel1.addConsumedResourceRelation(appRel2, resRelType, null, ForeignableOwner.AMW)); - entityManager.persist(asRel2.addConsumedResourceRelation(appRel1, resRelType, null, ForeignableOwner.AMW)); - entityManager.persist(asRel2.addConsumedResourceRelation(appRel2, resRelType, null, ForeignableOwner.AMW)); - Mockito.when(applistScreenDomainService.getAppServerResourcesWithApplications(Mockito.anyString(), - Mockito.anyInt(), Mockito.anyBoolean())).thenReturn( - Arrays.asList(asRel1,asRel2)); - service.dependencyResolverService = dependencyResolver; - } - - @Test - public void testGetAppServersWithApplications() { - service.getAppServersWithApplications("app", null, release1); - } - -} diff --git a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsTest.java b/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsTest.java index 876ce947b..c27d457a5 100644 --- a/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsTest.java +++ b/AMW_business/src/test/java/ch/puzzle/itc/mobiliar/business/resourcegroup/boundary/ResourceRelationsTest.java @@ -30,6 +30,7 @@ import ch.puzzle.itc.mobiliar.business.usersettings.control.UserSettingsService; import ch.puzzle.itc.mobiliar.business.usersettings.entity.UserSettingsEntity; import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; +import ch.puzzle.itc.mobiliar.common.util.Tuple; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -86,8 +87,8 @@ public void testGetAppServersWithApplications() throws Exception { Mockito.when(userSettingsService.getUserSettings(Mockito.anyString())).thenReturn(userSettings); List aslist = Arrays.asList(as); Mockito.when(applistScreenDomainService.getAppServerResourcesWithApplications(Mockito.isNull(), - Mockito.isNull(), Mockito.anyBoolean())).thenReturn(aslist); - service.getAppServersWithApplications(null, null, release); + Mockito.isNull(), Mockito.isNull(), Mockito.anyBoolean())).thenReturn(new Tuple<>(aslist,0L)); + service.getAppServersWithApplications(null, null, null, release); Mockito.verify(service).filterAppServersByRelease(release, aslist); } @@ -141,4 +142,4 @@ private ResourceWithRelations doTestFilterApplicationsByRelease(int appId) service.filterApplicationsByRelease(release, as, resourceWithRelations); return resourceWithRelations; } -} \ No newline at end of file +} diff --git a/AMW_e2e/cypress/e2e/apps/create.cy.js b/AMW_e2e/cypress/e2e/apps/create.cy.js new file mode 100644 index 000000000..90a7c5179 --- /dev/null +++ b/AMW_e2e/cypress/e2e/apps/create.cy.js @@ -0,0 +1,15 @@ +describe("Apps -CRUD", () => { + it("should create, read apps and appServers", () => { + cy.visit("AMW_angular/#/apps", { + auth: { + username: "admin", + password: "admin", + }, + }); + cy.get('[data-cy="button-add-app"]').click(); + cy.get("#name").type("test-app"); + cy.get('[data-cy="button-save"]').should("be.disabled"); + cy.get("#selectRelease").click({ force: true }); + cy.get('[data-cy="button-cancel"]').click(); + }); +}); diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java index dc6721d28..840b67a15 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java @@ -21,6 +21,7 @@ package ch.mobi.itc.mobiliar.rest; import ch.mobi.itc.mobiliar.rest.Analyze.TestGenerationRest; +import ch.mobi.itc.mobiliar.rest.apps.AppsRest; import ch.mobi.itc.mobiliar.rest.auditview.AuditViewRest; import ch.mobi.itc.mobiliar.rest.deployments.DeploymentDtoCsvBodyWriter; import ch.mobi.itc.mobiliar.rest.deployments.DeploymentsLogRest; @@ -53,6 +54,7 @@ public Set> getClasses() { private void addRestResourceClasses(Set> resources) { // Endpoints + resources.add(AppsRest.class); resources.add(BatchJobRest.class); resources.add(ResourcesRest.class); resources.add(ResourceGroupsRest.class); diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java new file mode 100644 index 000000000..fe38f3f92 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java @@ -0,0 +1,92 @@ +package ch.mobi.itc.mobiliar.rest.apps; + +import ch.mobi.itc.mobiliar.rest.dtos.AppAppServerDTO; +import ch.mobi.itc.mobiliar.rest.dtos.AppDTO; +import ch.mobi.itc.mobiliar.rest.dtos.AppServerDTO; +import ch.puzzle.itc.mobiliar.business.apps.boundary.*; +import ch.puzzle.itc.mobiliar.business.releasing.boundary.ReleaseLocator; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; +import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; +import ch.puzzle.itc.mobiliar.common.util.Tuple; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +import static javax.ws.rs.core.Response.Status.*; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + + +@RequestScoped +@Path("/apps") +@Api(value = "/apps", description = "Application servers and apps") +public class AppsRest { + + @Inject + private ListAppsUseCase listAppsUseCase; + + + @Inject + private AddAppUseCase addAppUseCase; + + @Inject + private AddAppWithServerUseCase addAppWithServerUseCase; + + @Inject + private AddAppServerUseCase addAppServerUseCase; + + + @GET + @ApiOperation(value = "Get applicationservers and apps", notes = "Returns all apps") + @Produces(MediaType.APPLICATION_JSON) + public Response getApps(@QueryParam("start") Integer start, + @QueryParam("limit") Integer limit, + @QueryParam("appServerName") String filter, + @NotNull @QueryParam("releaseId") Integer releaseId) throws NotFoundException { + Tuple, Long> result = listAppsUseCase.appsFor(start, limit, filter, releaseId); + + return Response.status(OK).entity(appServersToResponse(result.getA())).header("X-total-count", result.getB()).build(); + } + + private List appServersToResponse(List apps) { + List appServerList = new ArrayList<>(apps.size()); + for (ResourceWithRelations app : apps) { + appServerList.add(new AppServerDTO(app)); + } + return appServerList; + } + + @POST + @ApiOperation(value = "Add a application") + public Response addApp(@NotNull @QueryParam("appName") String appName, @NotNull @QueryParam("releaseId") Integer releaseId) throws NotFoundException, IllegalArgumentException, IllegalStateException { + AddAppCommand addAppCommand = new AddAppCommand(appName, releaseId); + return Response.status(CREATED).entity(addAppUseCase.add(addAppCommand)).build(); + } + + @Path("/appServer") + @POST + @ApiOperation(value = "Add a applicationserver") + public Response addAppServer(@NotNull @QueryParam("appServerName") String name, @NotNull @QueryParam("releaseId") Integer releaseId) throws NotFoundException, IllegalArgumentException, IllegalStateException { + AppServerCommand appServerCommand = new AppServerCommand(name, releaseId); + return Response.status(CREATED).entity(addAppServerUseCase.add(appServerCommand)).build(); + } + + + @Path("/appWithServer") + @POST + @ApiOperation(value = "Add a application with appServer") + public Response addAppWithAppServer(@NotNull @ApiParam() AppAppServerDTO app) throws NotFoundException, IllegalArgumentException, IllegalStateException { + AddAppWithServerCommand addAppWithServerCommand = + new AddAppWithServerCommand(app.getAppName(), app.getAppReleaseId(), app.getAppServerId(), app.getAppServerReleaseId()); + return Response.status(CREATED).entity(addAppWithServerUseCase.add(addAppWithServerCommand)).build(); + } +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java new file mode 100644 index 000000000..e6cd839ed --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java @@ -0,0 +1,30 @@ +package ch.mobi.itc.mobiliar.rest.dtos; + +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; +import io.swagger.models.auth.In; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + + + +@XmlRootElement(name = "appAppServer") +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppAppServerDTO { + String appName; + Integer appReleaseId; + Integer appServerId; + Integer appServerReleaseId; + + public AppAppServerDTO(String appName, Integer appReleaseId) { + this.appName = appName; + this.appServerId = appReleaseId; + } +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppDTO.java new file mode 100644 index 000000000..a293c9181 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppDTO.java @@ -0,0 +1,29 @@ +package ch.mobi.itc.mobiliar.rest.dtos; + +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "app") +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppDTO { + Integer id; + String name; + ReleaseDTO release; + + public AppDTO(ResourceEntity app) { + this.id = app.getId(); + this.name = app.getName(); + if (app.getRelease() != null) { + this.release = new ReleaseDTO(app.getRelease().getId(), app.getRelease().getName()); + } + } +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppServerDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppServerDTO.java new file mode 100644 index 000000000..27b2f9cf4 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppServerDTO.java @@ -0,0 +1,46 @@ +package ch.mobi.itc.mobiliar.rest.dtos; + +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; +import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.List; + + +@XmlRootElement(name = "appServer") +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppServerDTO { + + Integer id; + String name; + Boolean deletable; + String runtimeName; + ReleaseDTO release; + List apps; + + public AppServerDTO(ResourceWithRelations appServer) { + this.id = appServer.getResource().getId(); + this.name = appServer.getResource().getName(); + this.deletable = appServer.getResource().isDeletable(); + this.runtimeName = appServer.getResource().getRuntime() != null ? appServer.getResource().getRuntime().getName() : ""; + if (appServer.getResource().getRelease() != null) { + this.release = new ReleaseDTO(appServer.getResource().getRelease().getId(), appServer.getResource().getRelease().getName()); + } + + this.apps = new ArrayList<>(); + for (ResourceEntity app : appServer.getRelatedResources()) { + this.apps.add(new AppDTO(app)); + } + + + } +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ReleaseDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ReleaseDTO.java new file mode 100644 index 000000000..bd6355931 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ReleaseDTO.java @@ -0,0 +1,20 @@ +package ch.mobi.itc.mobiliar.rest.dtos; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "release") +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ReleaseDTO { + Integer id; + String name; +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/exceptions/IllegalStateExceptionMapper.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/exceptions/IllegalStateExceptionMapper.java index d6c6e8c58..1d3cde012 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/exceptions/IllegalStateExceptionMapper.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/exceptions/IllegalStateExceptionMapper.java @@ -28,6 +28,6 @@ public class IllegalStateExceptionMapper implements ExceptionMapper { @Override public Response toResponse(IllegalStateException exception) { - return Response.status(Response.Status.FORBIDDEN).entity(new ExceptionDto(exception)).build(); + return Response.status(Response.Status.CONFLICT).entity(new ExceptionDto(exception)).build(); } } diff --git a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistFilter.java b/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistFilter.java deleted file mode 100644 index b567a8871..000000000 --- a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistFilter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * AMW - Automated Middleware allows you to manage the configurations of - * your Java EE applications on an unlimited number of different environments - * with various versions, including the automated deployment of those apps. - * Copyright (C) 2013-2016 by Puzzle ITC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ch.puzzle.itc.mobiliar.presentation.applist; - -import lombok.Getter; -import lombok.Setter; - -import javax.enterprise.context.SessionScoped; -import javax.inject.Named; -import java.io.Serializable; - -/** - * Holds the filter criteria for the applist screen in the session - */ -@Named -@SessionScoped -public class ApplistFilter implements Serializable { - private static final long serialVersionUID = 1L; - - @Getter - @Setter - private String filter = " "; - - @Getter - @Setter - private Integer releaseId = null; - - @Getter - @Setter - private Integer maxResults = 20; - -} diff --git a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistView.java b/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistView.java deleted file mode 100644 index e72f50276..000000000 --- a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/ApplistView.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * AMW - Automated Middleware allows you to manage the configurations of - * your Java EE applications on an unlimited number of different environments - * with various versions, including the automated deployment of those apps. - * Copyright (C) 2013-2016 by Puzzle ITC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ch.puzzle.itc.mobiliar.presentation.applist; - -import java.io.Serializable; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.ejb.EJBException; -import javax.inject.Inject; - -import ch.puzzle.itc.mobiliar.business.foreignable.entity.ForeignableAttributesDTO; -import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceBoundary; -import ch.puzzle.itc.mobiliar.business.security.boundary.PermissionBoundary; -import ch.puzzle.itc.mobiliar.business.security.entity.Action; -import lombok.Getter; -import lombok.Setter; - -import org.apache.commons.lang3.StringUtils; - -import ch.puzzle.itc.mobiliar.business.foreignable.boundary.ForeignableBoundary; -import ch.puzzle.itc.mobiliar.business.foreignable.entity.ForeignableOwner; -import ch.puzzle.itc.mobiliar.business.foreignable.entity.ForeignableOwnerViolationException; -import ch.puzzle.itc.mobiliar.business.generator.control.extracted.ResourceDependencyResolverService; -import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; -import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceRelations; -import ch.puzzle.itc.mobiliar.business.resourcegroup.control.ResourceTypeProvider; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; -import ch.puzzle.itc.mobiliar.business.security.entity.Permission; -import ch.puzzle.itc.mobiliar.common.exception.NotAuthorizedException; -import ch.puzzle.itc.mobiliar.common.exception.ResourceNotFoundException; -import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; -import ch.puzzle.itc.mobiliar.presentation.ViewBackingBean; -import ch.puzzle.itc.mobiliar.presentation.common.ApplicationCreatorDataProvider; -import ch.puzzle.itc.mobiliar.presentation.common.ReleaseSelectionDataProvider; -import ch.puzzle.itc.mobiliar.presentation.common.ReleaseSelector; -import ch.puzzle.itc.mobiliar.presentation.resourcesedit.CreateResourceController; -import ch.puzzle.itc.mobiliar.presentation.util.GlobalMessageAppender; -import ch.puzzle.itc.mobiliar.presentation.util.NavigationUtils; - -@ViewBackingBean -public class ApplistView implements Serializable, ApplicationCreatorDataProvider { - - private static final long serialVersionUID = 8800041104451876453L; - - @Inject @Getter - private ReleaseSelectionDataProvider releaseDataProvider; - @Inject - CreateResourceController createResourceController; - @Inject - ResourceTypeProvider resourceTypeProvider; - @Inject - ResourceDependencyResolverService resourceDependencyResolver; - @Inject - ResourceRelations resourceRelations; - @Inject - ApplistFilter applistFilter; - @Inject - ResourceBoundary resourceBoundary; - - @Getter @Setter - private String filter; - @Getter @Setter - private Integer selection; - @Getter @Setter - private Integer selectionApp; - @Getter @Setter - private String appServerName; - @Getter @Setter - private List appServerList; - @Getter @Setter - private boolean relation; - @Setter - private Integer maxResults; - @Getter - private ReleaseSelector asReleaseSelector; - @Getter - private ReleaseSelector filterReleaseSelector; - - @Inject - private PermissionBoundary permissionBoundary; - - @Inject - private ForeignableBoundary foreignableBoundary; - - @PostConstruct - public void init() { - if (filterReleaseSelector == null) { - filterReleaseSelector = new ReleaseSelector(null, releaseDataProvider.getReleaseMap()); - } - if (asReleaseSelector == null) { - asReleaseSelector = new ReleaseSelector(releaseDataProvider.getUpcomingReleaseId(), - releaseDataProvider.getReleaseMap()); - } - releaseDataProvider.reset(); - - if (StringUtils.isEmpty(filter)) { - this.filter = applistFilter.getFilter(); - } - if (maxResults == null) { - this.maxResults = applistFilter.getMaxResults(); - } - if (filterReleaseSelector.getSelectedReleaseId() == null) { - if (applistFilter.getReleaseId() != null) { - this.filterReleaseSelector.setSelectedReleaseId(applistFilter.getReleaseId()); - } - else { - this.filterReleaseSelector.setSelectedReleaseId(releaseDataProvider - .getUpcomingReleaseId()); - } - } - loadAppServerList(); - } - - - - /** - * Applikation entfernen - */ - public void removeApp() { - if (removeApp(selectionApp)) { - clearPopupFields(); - loadAppServerList(); - } - } - - /** - * Entfernen einer Applikationsgruppe. - */ - public void removeAppServer() { - if (removeAppServer(selection)) { - clearPopupFields(); - loadAppServerList(); - } - } - - public void createAppServer() { - if (createResourceController.createResource(appServerName, resourceTypeProvider - .getOrCreateDefaultResourceType(DefaultResourceTypeDefinition.APPLICATIONSERVER), - asReleaseSelector.getSelectedRelease())) { - clearPopupFields(); - loadAppServerList(); - } - clearPopupFields(); - } - - public void loadAppServerList() { - appServerList = loadAppServers(filter, getMaxResults() == 0 ? null : getMaxResults(), - filterReleaseSelector.getSelectedRelease()); - } - - public String doFilter() { - StringBuilder query = new StringBuilder(); - - query.append("release=").append(filterReleaseSelector.getSelectedReleaseId()); - applistFilter.setReleaseId(filterReleaseSelector.getSelectedReleaseId()); - - query.append("&maxResults="); - if (maxResults != null) { - query.append(maxResults); - applistFilter.setMaxResults(maxResults); - } - else { - query.append('0'); - applistFilter.setMaxResults(0); - } - - query.append("&filter="); - if (filter != null && !filter.trim().isEmpty()) { - query.append(filter.trim()); - applistFilter.setFilter(filter.trim()); - } - else { - query.append(' '); - applistFilter.setFilter(" "); - } - return NavigationUtils.getRefreshOutcomeWithAdditionalParam(query.toString()); - } - public void setRelease(Integer releaseId) { - this.filterReleaseSelector.setSelectedReleaseId(releaseId); - } - - private void clearPopupFields() { - appServerName = null; - } - - public Integer getMaxResults() { - return maxResults == null ? 0 : maxResults; - } - - @Override - public void afterAddingAppOrAs() { - loadAppServerList(); - clearPopupFields(); - } - - /** - * Applikation entfernen - */ - private boolean removeApp(Integer applicationId) { - boolean isSuccessful = false; - try { - if (applicationId == null) { - String errorMessage = "No application selected."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } else { - try{ - resourceBoundary.removeResource(ForeignableOwner.getSystemOwner(), applicationId); - String message = "Application successfully deleted"; - GlobalMessageAppender.addSuccessMessage(message); - isSuccessful = true; - } catch(EJBException e) { - if (e.getCause() instanceof NotAuthorizedException) { - GlobalMessageAppender.addErrorMessage(e.getCause().getMessage()); - } else { - throw e; - } - } - } - } catch (ResourceNotFoundException e) { - String errorMessage = "Could not load selected application for deletation."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } catch (ForeignableOwnerViolationException e){ - GlobalMessageAppender.addErrorMessage("Application with id "+applicationId+" can not be deleted by owner "+e.getViolatingOwner()); - } catch (Exception e) { - String errorMessage = "Could not delete selected application."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } - return isSuccessful; - } - - /** - * Entfernen einer Applikationsgruppe. - */ - private boolean removeAppServer(Integer selectedAppServerId) { - - boolean isSuccessful = false; - try { - if (selectedAppServerId == null) { - String errorMessage = "No application server selected."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } else { - try{ - resourceBoundary.deleteApplicationServerById(selectedAppServerId); - String message = "Applicationserver successfully deleted"; - GlobalMessageAppender.addSuccessMessage(message); - isSuccessful = true; - } catch(EJBException e) { - if (e.getCause() instanceof NotAuthorizedException) { - GlobalMessageAppender.addErrorMessage(e.getCause().getMessage()); - } else { - throw e; - } - } - } - } catch (ResourceNotFoundException e) { - String errorMessage = "Could not load selected server for deletion."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } catch (Exception e) { - String errorMessage = "Could not delete selected application server."; - GlobalMessageAppender.addErrorMessage(errorMessage); - } - return isSuccessful; - } - - private List loadAppServers(String filter, Integer maxResults, ReleaseEntity release) { - if(maxResults!=null && maxResults==0) { - maxResults = null; - } - return resourceRelations.getAppServersWithApplications(filter, maxResults, release); - } - - public boolean canCreateApplicationServerInstance(){ - return permissionBoundary.canCreateResourceInstance(DefaultResourceTypeDefinition.APPLICATIONSERVER); - } - - public boolean canCreateApplicationInstance(){ - return permissionBoundary.canCreateResourceInstance(DefaultResourceTypeDefinition.APPLICATION); - } - - public boolean canShowDeleteApp(ResourceEntity app){ - return permissionBoundary.hasPermission(Permission.RESOURCE, null, Action.DELETE, app, null) && foreignableBoundary.isModifiableByOwner(ForeignableOwner.getSystemOwner(), app); - } - - public boolean canShowDeleteAppServer(ResourceWithRelations appServer){ - ResourceEntity appserverResource = appServer.getResource(); - return appserverResource.isDeletable() && permissionBoundary.hasPermission(Permission.RESOURCE, null, Action.DELETE, appserverResource, null) && foreignableBoundary.isModifiableByOwner(ForeignableOwner.getSystemOwner(), appserverResource); - } - - public ForeignableAttributesDTO getForeignableAttributes(ResourceEntity app){ - return new ForeignableAttributesDTO(app.getOwner(), app.getExternalKey(), app.getExternalLink()); - } - -} diff --git a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/CreateApplicationForAsPopup.java b/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/CreateApplicationForAsPopup.java deleted file mode 100644 index dc7e7423d..000000000 --- a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/applist/CreateApplicationForAsPopup.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * AMW - Automated Middleware allows you to manage the configurations of - * your Java EE applications on an unlimited number of different environments - * with various versions, including the automated deployment of those apps. - * Copyright (C) 2013-2016 by Puzzle ITC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ch.puzzle.itc.mobiliar.presentation.applist; - -import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; -import ch.puzzle.itc.mobiliar.business.resourcegroup.control.ResourceGroupPersistenceService; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceGroup; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceGroupEntity; -import ch.puzzle.itc.mobiliar.common.util.ApplicationServerContainer; -import ch.puzzle.itc.mobiliar.common.util.DefaultResourceTypeDefinition; -import ch.puzzle.itc.mobiliar.presentation.CompositeBackingBean; -import ch.puzzle.itc.mobiliar.presentation.common.ApplicationCreatorDataProvider; -import ch.puzzle.itc.mobiliar.presentation.common.ReleaseSelectionDataProvider; -import ch.puzzle.itc.mobiliar.presentation.common.ReleaseSelector; -import ch.puzzle.itc.mobiliar.presentation.common.UnsuccessfulActionException; -import ch.puzzle.itc.mobiliar.presentation.resourcesedit.CreateResourceController; -import lombok.Getter; -import lombok.Setter; - -import javax.inject.Inject; -import java.io.Serializable; -import java.util.*; - -@CompositeBackingBean -public class CreateApplicationForAsPopup implements Serializable { - private static final long serialVersionUID = 1L; - - @Inject - @Getter - private ReleaseSelectionDataProvider releaseDataProvider; - - @Inject - CreateResourceController createResourceController; - - @Inject - ResourceGroupPersistenceService resourceGroupService; - - private List asGroupsForSelectBox; - private Map asGroupsForSelectBoxMap; - @Getter - private ReleaseSelector appReleaseSelector; - - @Getter - @Setter - private String appName; - @Getter - private Integer appServerGroupId; - - private Integer asReleaseId; - - public void setAsReleaseId(Integer releaseId){ - this.asReleaseId=releaseId; - } - - public Integer getAsReleaseId(){ - return this.asReleaseId; - } - - @Getter - boolean loadList = false; - - public void init() { - // workaround while other dataproviders are still session scoped - appReleaseSelector = new ReleaseSelector(releaseDataProvider.getUpcomingReleaseId(), releaseDataProvider.getReleaseMap()); - asGroupsForSelectBox = null; - appName = null; - appServerGroupId = null; - asReleaseId = null; - loadList = true; - } - - public List getAsGroupsForSelectBox() { - if (loadList) { - asGroupsForSelectBox = new ArrayList(getApplicationServerGroupsForSelectBox()); - asGroupsForSelectBoxMap = new HashMap(); - for (ResourceGroup g : asGroupsForSelectBox) { - asGroupsForSelectBoxMap.put(g.getId(), g); - } - } - return asGroupsForSelectBox; - } - - public List getReleasesForAs() { - if (appServerGroupId != null && asGroupsForSelectBoxMap.containsKey(appServerGroupId)) { - return asGroupsForSelectBoxMap.get(appServerGroupId).getReleases(); - } - asReleaseId = null; - return null; - } - - public void createAppAndAppServer(ApplicationCreatorDataProvider parentDataProvider) - throws UnsuccessfulActionException { - if(createResourceController.createAppAndAppServer(appName, appServerGroupId, appReleaseSelector.getSelectedRelease(), asReleaseId)&&parentDataProvider!=null){ - parentDataProvider.afterAddingAppOrAs(); - } - init(); - - } - - public void setAppServerGroupId(Integer appServerGroupId) { - // jsf returns 0 for null, therefore we better use -1 to indicate nothing selected - if (appServerGroupId != null && appServerGroupId >= 0) { - this.appServerGroupId = appServerGroupId; - } - else { - this.appServerGroupId = null; - } - } - - private Set getApplicationServerGroupsForSelectBox(){ - SortedSet groups = new TreeSet<>(); - List result; - result = resourceGroupService.loadGroupsForTypeName(DefaultResourceTypeDefinition.APPLICATIONSERVER.name()); - for (ResourceGroupEntity g : result) { - if (!ApplicationServerContainer.APPSERVERCONTAINER.getDisplayName().equals(g.getName())) { - groups.add(ResourceGroup.createByResource(g)); - } - } - return groups; - } - -} diff --git a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/util/UserSettings.java b/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/util/UserSettings.java index 376b12a2d..6629d1307 100644 --- a/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/util/UserSettings.java +++ b/AMW_web/src/main/java/ch/puzzle/itc/mobiliar/presentation/util/UserSettings.java @@ -24,7 +24,6 @@ import ch.puzzle.itc.mobiliar.business.usersettings.control.UserSettingsService; import ch.puzzle.itc.mobiliar.business.usersettings.entity.UserSettingsEntity; - import javax.annotation.PostConstruct; import javax.enterprise.context.SessionScoped; import javax.inject.Inject; diff --git a/AMW_web/src/main/webapp/index.html b/AMW_web/src/main/webapp/index.html index 6b2607871..9f2226185 100644 --- a/AMW_web/src/main/webapp/index.html +++ b/AMW_web/src/main/webapp/index.html @@ -3,7 +3,7 @@ - + Automation Middleware diff --git a/AMW_web/src/main/webapp/pages/applist.xhtml b/AMW_web/src/main/webapp/pages/applist.xhtml deleted file mode 100644 index 59bd65354..000000000 --- a/AMW_web/src/main/webapp/pages/applist.xhtml +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - - - - - - Apps - - - - - - - - - -
    -
    - -
    - -

    Filter

    -
    - -
    - -
    -
    -
    - -
    - - - - -
    -
    -
    - - -

    - -

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - - -

    - -

    -
    - - - x - - - - - - Are you sure you want to delete the application release? - - - -
    - - - -

    - -

    -
    - - - x - - - - - - Are you sure you want to delete the application server release? - - - -
    - -
    -
    - -
    - - - - -

    - -

    -
    - - - x - - - - - - - - - - - - - - - -
    - -
    -
    - - - \ No newline at end of file diff --git a/AMW_web/src/main/webapp/pages/templates/template.xhtml b/AMW_web/src/main/webapp/pages/templates/template.xhtml index 39af276ab..9352e3e53 100644 --- a/AMW_web/src/main/webapp/pages/templates/template.xhtml +++ b/AMW_web/src/main/webapp/pages/templates/template.xhtml @@ -130,8 +130,14 @@