[DEV] add application add and remove ==> no EDIT

This commit is contained in:
Edouard DUPIN 2023-01-29 23:14:48 +01:00
parent c0ab443c43
commit a4fe1f1ed1
16 changed files with 674 additions and 20 deletions

View File

@ -4,8 +4,8 @@ import org.kar.archidata.SqlWrapper;
import org.kar.archidata.filter.GenericContext;
import org.kar.karso.model.*;
import org.kar.archidata.util.JWTWrapper;
import org.kar.archidata.annotation.security.PermitAll;
import org.kar.archidata.annotation.security.RolesAllowed;
import org.kar.archidata.exception.InputException;
import java.util.List;
@ -16,7 +16,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
@Path("/application")
@Produces( MediaType.APPLICATION_JSON)
public class ApplicationResource {
@ -30,6 +29,7 @@ public class ApplicationResource {
System.out.println("getApplications");
return SqlWrapper.gets(Application.class, false);
}
@GET
@Path("small")
@RolesAllowed(value= {"USER", "ADMIN"})
@ -38,7 +38,6 @@ public class ApplicationResource {
return SqlWrapper.gets(ApplicationSmall.class, false);
}
@GET
@Path("get_token")
@RolesAllowed(value= {"USER", "ADMIN"})
@ -85,6 +84,7 @@ public class ApplicationResource {
}
return Response.status(201).entity("{ \"url\":\"" + returnAdress + "\", \"jwt\":\"" + ret + "\"}").build();
}
@GET
@Path("return")
@RolesAllowed(value= {"USER", "ADMIN"})
@ -131,6 +131,40 @@ public class ApplicationResource {
return Response.status(201).entity("{ \"url\":\"" + returnAdress + "\"}").build();
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
@Produces( value = MediaType.TEXT_PLAIN )
public void remove(@Context SecurityContext sc, @PathParam("id") long applicationId) throws Exception {
SqlWrapper.setDelete(Application.class, applicationId);
}
@POST
@RolesAllowed("ADMIN")
public Application create(Application application) throws Exception {
System.out.println("create new application " + application);
// verify login or email is correct:
if (application.name == null || application.name.length() < 6) {
throw new InputException("name", "create application (name too small: '" + application.name + "')");
}
if (application.redirect == null || application.redirect.length() < 6) {
throw new InputException("redirect", "create application (redirect too small: '" + application.redirect + "')");
}
application.id = null;
application.create_date = null;
application.deleted = null;
application.modify_date = null;
return SqlWrapper.insert(application);
}
@PUT
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Application put(@PathParam("id") Long id, String jsonRequest) throws Exception {
SqlWrapper.update(Application.class, id, jsonRequest);
return SqlWrapper.get(Application.class, id);
}
}

View File

@ -11,6 +11,7 @@ CREATE TABLE `application` (
*/
import org.kar.archidata.annotation.SQLComment;
import org.kar.archidata.annotation.SQLDefault;
import org.kar.archidata.annotation.SQLIfNotExists;
import org.kar.archidata.annotation.SQLLimitSize;
import org.kar.archidata.annotation.SQLNotNull;
@ -23,20 +24,23 @@ import com.fasterxml.jackson.annotation.JsonInclude;
@SQLIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Application extends GenericTable{
@SQLLimitSize(512)
@SQLLimitSize(256)
public String name;
@SQLLimitSize(512)
@SQLLimitSize(2048)
public String description;
@SQLLimitSize(512)
@SQLLimitSize(2048)
@SQLNotNull
public String redirect;
@SQLLimitSize(512)
@SQLLimitSize(2048)
@SQLDefault("http://localhost:4200/sso/")
public String redirectDev;
@SQLLimitSize(512)
@SQLLimitSize(2048)
@SQLDefault("http://localhost:4200/sso/notification")
public String notification;
@SQLNotNull
@SQLComment("Expiration time ")
public int ttl;
@SQLDefault("666")
public Integer ttl;
public Application() {
}
@ -54,3 +58,10 @@ public class Application extends GenericTable{
'}';
}
}
/*
ALTER TABLE `application`
CHANGE `description` `description` varchar(2048) COLLATE 'utf8mb4_0900_ai_ci' NULL COMMENT 'description of the application' AFTER `name`,
CHANGE `redirect` `redirect` varchar(2048) COLLATE 'latin1_bin' NOT NULL COMMENT 'Token (can be not unique)' AFTER `description`,
CHANGE `redirectDev` `redirectDev` varchar(2048) COLLATE 'latin1_bin' NOT NULL DEFAULT 'http://localhost:4200/sso/' AFTER `redirect`,
CHANGE `notification` `notification` varchar(2048) COLLATE 'latin1_bin' NOT NULL DEFAULT 'http://localhost:4200/sso/notification' AFTER `redirectDev`;
*/

View File

@ -17,6 +17,7 @@ import {
SignUpScene,
HomeUnregisteredScene,
ManageAccountsScene,
ManageApplicationScene,
} from './scene';
import { OnlyAdminGuard, OnlyUnregisteredGuardHome, OnlyUsersGuard, OnlyUsersGuardHome } from 'common/service/session';
import { ForbiddenScene, NotFound404Scene } from 'common/scene';
@ -72,6 +73,11 @@ const routes: Routes = [
component: ManageAccountsScene,
canActivate: [OnlyAdminGuard],
},
{
path: 'manage_applications',
component: ManageApplicationScene,
canActivate: [OnlyAdminGuard],
},
{
path: '**',
component: NotFound404Scene,

View File

@ -112,6 +112,13 @@ export class AppComponent implements OnInit {
title: 'Manage Accounts',
navigateTo: 'manage_accounts',
enable: this.sessionService.userAdmin === true,
},{
position: MenuPosition.LEFT,
hover: 'Admin of all the applications',
icon: 'app_registration',
title: 'Manage Applications',
navigateTo: 'manage_applications',
enable: this.sessionService.userAdmin === true,
},
],
},

View File

@ -23,11 +23,13 @@ import {
SettingsScene,
HomeUnregisteredScene,
ManageAccountsScene,
ManageApplicationScene,
} from 'app/scene';
import {
BddService,
CookiesService,
HttpWrapperService,
NotificationService,
OnlyAdminGuard,
OnlyUnregisteredGuardHome,
OnlyUsersGuard,
@ -74,6 +76,7 @@ import { PopInUploadProgress, PopInDeleteConfirm } from 'common/popin';
ChangePasswordScene,
HomeUnregisteredScene,
ManageAccountsScene,
ManageApplicationScene,
],
imports: [
BrowserModule,
@ -99,6 +102,7 @@ import { PopInUploadProgress, PopInDeleteConfirm } from 'common/popin';
SessionService,
UserService,
SSOService,
NotificationService,
SettingsService,
OnlyUsersGuard,
OnlyAdminGuard,

View File

@ -6,7 +6,7 @@
import { Component, OnInit } from '@angular/core';
import { ApplicationService } from 'app/service';
import { GetApplicationSmallnResponse, SpecificTokenResponse } from 'app/service/application';
import { GetApplicationSmallResponse, SpecificTokenResponse } from 'app/service/application';
import { UserService } from 'common/service';
@Component({
@ -16,21 +16,21 @@ import { UserService } from 'common/service';
})
export class HomeScene implements OnInit {
error = '';
dataList: GetApplicationSmallnResponse[];
dataList: GetApplicationSmallResponse[];
constructor(private applicationService: ApplicationService, private userService: UserService) {}
ngOnInit() {
let self = this;
this.applicationService
.getApplicationsSmall()
.then((data: GetApplicationSmallnResponse[]) => {
.then((data: GetApplicationSmallResponse[]) => {
self.dataList = data;
})
.catch(error => {
console.log(`fail to keep data : ${error}`);
});
}
onClick(_event: any, data: GetApplicationSmallnResponse): void {
onClick(_event: any, data: GetApplicationSmallResponse): void {
//window.location.href = data.redirect;
let self = this;
this.applicationService

View File

@ -5,6 +5,7 @@ import { HelpScene } from './help/help';
import { HomeUnregisteredScene } from './home-unregistered/home-unregistered';
import { HomeScene } from './home/home';
import { ManageAccountsScene } from './manage-accounts/manage-accounts';
import { ManageApplicationScene } from './manage-application/manage-application';
import { SettingsScene } from './settings/settings';
import { SignInScene } from './sign-in/sign-in';
import { SignOutScene } from './sign-out/sign-out';
@ -24,4 +25,5 @@ export {
SettingsScene,
HomeUnregisteredScene,
ManageAccountsScene,
ManageApplicationScene,
};

View File

@ -0,0 +1,97 @@
<div class="generic-page">
<div class="title">Manage Applications</div>
<div class="fill-all">
<div>
<div class="settings-title">
<div class="group-title">Application</div>
<div class="group-description">Availlable applications for this SSO</div>
</div>
<div class="settings-elements">
<table class="table-model">
<tr>
<th>id</th>
<th>name</th>
<th>Description</th>
<th>redirect</th>
<th>TTL</th>
<th>Notification</th>
<th>Actions</th>
</tr>
<tr *ngFor="let application of applications">
<td>{{application.id}}</td>
<td>{{application.name}}</td>
<td>{{application.description}}</td>
<td>{{application.redirect}}</td>
<td>{{application.ttl}}</td>
<td>{{application.notification}}</td>
<td>
<button
class="square-button login color-shadow-black"
(click)="onEditApplication($event, application)"
type="submit">
<i class="material-icons">edit</i>
</button>&nbsp;
<button
class="square-button login color-button-cancel color-shadow-black"
(click)="onRemoveApplication($event, application)"
type="submit">
<i class="material-icons">delete_forever</i>
</button>
</td>
</tr>
</table>
</div>
<div class="settings-validation">
</div>
</div>
</div>
<div class="clear"><br/></div>
<div style="padding-top:15px;">
<div class="settings-title">
<div class="group-title">Create new application</div>
<div class="group-description">Add a new application on the server</div>
</div>
<div class="settings-elements">
<table width="100%">
<tr>
<td width="15%"><b>Name:</b></td>
<td width="85%">
<app-entry
[value]="name"
placeholder="Enter application name"
[hasError]="nameState !== true"
(changeValue)="checkName($event)"></app-entry>
<app-error-message-state [value]="nameState"></app-error-message-state>
</td>
</tr>
<tr>
<td width="15%"><b>Redirect:</b></td>
<td width="85%">
<app-entry
[value]="redirect"
placeholder="Enter http redirect adresses "
[hasError]="redirectState !== true"
(changeValue)="checkRedirect($event)"></app-entry>
<app-error-message-state [value]="redirectState"></app-error-message-state>
</td>
</tr>
</table>
</div>
<div class="settings-validation">
<button
class="button login color-button-validate color-shadow-black"
id="create-button"
[disabled]="validateButtonCreateApplicationDisabled"
(click)="onCreateApplication()"
type="submit">
Create application
</button>
</div>
</div>
<div class="clear"></div>
</div>
<delete-confirm
[comment]="confirmDeleteComment"
(callback)="deleteConfirmed()"></delete-confirm>

View File

@ -0,0 +1,164 @@
.settings-title {
width: 80%;
border: solid;
border-width: 1px;
margin: auto;
padding: 10px 20px;
border-color: black;
border-radius: 10px 10px 0px 0px;
background-color: gray;
.group-title {
font-size: 24px;
font-weight: bold;
line-height: 24px;
vertical-align: middle;
margin: 10px 0 10px 0;
text-align: Left;
}
.group-description {
font-size: 16px;
line-height: 16px;
vertical-align: middle;
margin: 10px 0 10px 0;
text-align: Left;
}
}
.settings-elements {
width: 80%;
border: solid;
border-width: 0px 1px;
margin: auto;
padding: 10px 20px;
border-color: black;
border-radius: 0px;
background-color: lightgray;
vertical-align: middle;
text-align: Left;
.elem-hr {
width: 100%;
border-color: black;
border: dashed;
border-width: 1px 0 0 0;
margin: 10px auto;
}
.elem-title {
font-size: 18px;
font-weight: bold;
margin: 0 10px 0 36px;
}
.elem-description {
font-size: 14px;
margin: 0 20px 10px 50px;
font-style: italic;
}
.elem-input {
font-size: 14px;
margin: 0 20px 10px 36px;
input {
width: 100%;
}
}
}
.settings-validation {
width: 80%;
border: solid;
border-width: 1px;
margin: auto;
padding: 10px 20px;
border-color: black;
border-radius: 0px 0px 10px 10px;
background-color: gray;
}
.title {
//background-color: green;
font-size: 45px;
font-weight: bold;
line-height: 60px;
width: 100%;
text-align: center;
vertical-align: middle;
margin: 10px 0 10px 0;
text-shadow: 1px 1px 2px white, 0 0 1em white, 0 0 0.2em white;
text-transform: uppercase;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
}
.item-home {
background-color: rgba(200, 200, 200, 0.5);
font-size: 20px;
height: 190px;
width: 200px;
margin: 5px;
padding: 0;
overflow: hidden;
// box-shadow: 0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);
box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.6);
line-height: normal;
border: none;
font-family: 'Roboto', 'Helvetica', 'Arial', 'sans-serif';
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0;
will-change: box-shadow;
outline: none;
cursor: pointer;
text-decoration: none;
text-align: center;
transition-duration: 0.4s;
float: left;
display: block;
h1 {
font-size: 24px;
}
&:hover {
background-color: rgba(200, 200, 200, 1);
//box-shadow: 0px 2px 4px 0 rgba(255, 0, 0, 0.6);
}
.material-icons {
vertical-align: middle;
}
.material-icons {
position: absolute;
top: 50%;
left: 50%;
transform: ~'translate(-12px,-12px)';
line-height: 24px;
width: 24px;
}
}
@media all and (min-width: 1310px) {
.colomn_mutiple {
width: ~'calc(210px * 5)';
margin: auto;
}
}
.table-model {
tr:nth-child(odd) {
background-color: rgb(180, 180, 180);
//color: #fff;
}
th {
background-color: darkgray;
text-align: center;
}
td {
text-align: center;
}
}
table {
width: 100%;
}

View File

@ -0,0 +1,192 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ApplicationService, ApplicationModel } from 'app/service';
import { AsyncActionState } from 'common/component';
import { NotificationService, PopInService } from 'common/service';
@Component({
selector: 'app-settings',
templateUrl: './manage-application.html',
styleUrls: ['./manage-application.less'],
changeDetection: ChangeDetectionStrategy.Default,
})
export class ManageApplicationScene implements OnInit {
applications: ApplicationModel[] = [];
constructor(
private applicationService: ApplicationService,
private notificationService: NotificationService,
private popInService: PopInService,
private cdr: ChangeDetectorRef,
) {
}
ngOnInit() {
let self = this;
this.applicationService
.gets()
.then((response: ApplicationModel[]) => {
console.log(`??? get full response: ${JSON.stringify(response, null, 4)}`);
self.applications = response;
})
.catch((error: any) => {
console.log(`??? get ERROR response: ${JSON.stringify(error, null, 4)}`);
});
}
onRemoveApplication(_event: any, application: ApplicationModel) {
this.confirmDeleteComment = `Delete the application ID: [${application.id}] ${application.name}`;
this.confirmDeleteApplication = application;
this.popInService.open('popin-delete-confirm');
}
removeApplicationConfirm(application: ApplicationModel) {
let self = this;
this.applicationService.remove(application.id)
.then(
() => {
const index = self.applications.indexOf(application, 0);
if (index > -1) {
self.applications.splice(index, 1);
self.cdr.detectChanges();
}
}
).catch(
(error: any) => {
self.notificationService.errorRaw(`Fail to delete application: [${application.id}]: ${application.name} ==> ${error}`)
}
);
}
confirmDeleteComment: string = undefined;
confirmDeleteApplication: ApplicationModel = undefined;
deleteConfirmed() {
if(this.confirmDeleteApplication !== undefined) {
this.removeApplicationConfirm(this.confirmDeleteApplication);
this.confirmDeleteComment = undefined;
this.confirmDeleteApplication = undefined;
}
}
onEditApplication(_event: any, application: ApplicationModel) {
/*
user.admin = value;
console.log(`onSetAdmin: ${value} on ${user.login}`);
user.adminState = AsyncActionState.LOADING;
this.cdr.detectChanges();
let self = this;
this.adminUserService.setAdmin(user.id, value)
.then(
() => {
user.adminState = AsyncActionState.DONE;
self.cdr.detectChanges();
setTimeout(() => {
user.adminState = undefined;
self.cdr.detectChanges();
}, 3000);
}
).catch(
(error: any) => {
user.adminState = AsyncActionState.FAIL;
self.cdr.detectChanges();
setTimeout(() => {
user.adminState = undefined;
user.admin = !value;
self.cdr.detectChanges();
}, 3000);
}
);
*/
}
public name: string = '';
public nameState: boolean|string = false;
public redirect: string = '';
public redirectState: boolean|string = false;
public validateButtonCreateApplicationDisabled: boolean = true;
/**
* update the state of the validation button. if all is OK, the button will became clickable
*/
updateButtonVisibility(): void {
if (this.nameState === true && this.redirectState === true) {
this.validateButtonCreateApplicationDisabled = false;
} else {
this.validateButtonCreateApplicationDisabled = true;
}
}
/**
* Check the login writing rules
*/
checkName(newValue: string): void {
this.name = newValue;
if (this.name.length < 6) {
this.nameState = "This name is too small >=6.";
}
this.nameState = true;
for (let iii=0; iii<this.applications.length; iii++) {
let application = this.applications[iii];
if (application.name === this.name) {
this.nameState = "This name already exist.";
break;
}
}
this.updateButtonVisibility();
}
/**
* Check the login writing rules
*/
checkRedirect(newValue: string): void {
this.redirect = newValue;
this.redirectState = true;
if (this.redirect.length <= 5) {
this.redirectState = "This redirect is too small.";
}
this.updateButtonVisibility();
}
createState: string | boolean = undefined;
/**
* Request the creation of a new application.
*/
onCreateApplication(): void {
console.log(`create user:`);
this.createState = AsyncActionState.LOADING;
let self = this;
this.applicationService.create(this.name, this.redirect)
.then(
(data: ApplicationModel) => {
self.createState = AsyncActionState.DONE;
console.log(`Get new user: ${JSON.stringify(data, null, 2)}`);
self.applications.push(data);
self.cdr.detectChanges();
self.name = null;
self.redirect = null;
setTimeout(() => {
this.createState = undefined;
}, 3000);
}
).catch(
(error: any) => {
self.createState = AsyncActionState.FAIL;
setTimeout(() => {
self.createState = undefined;
}, 3000);
}
);
}
}

View File

@ -29,12 +29,22 @@ export interface SpecificTokenResponse {
export interface SpecificReturnResponse {
url: string;
}
export interface GetApplicationSmallnResponse {
export interface GetApplicationSmallResponse {
name: string;
description: string;
redirect: string;
}
export interface ApplicationModel {
id: number;
name: string;
description: string;
redirect: string;
redirectDev: string;
notification: string;
ttl: number;
}
@Injectable()
export class ApplicationService {
constructor(private http: HttpWrapperService, private sessionService: SessionService) {
@ -96,7 +106,7 @@ export class ApplicationService {
});
});
}
getApplicationsSmall(): Promise<GetApplicationSmallnResponse[]> {
getApplicationsSmall(): Promise<GetApplicationSmallResponse[]> {
let self = this;
return new Promise((resolve, reject) => {
this.http
@ -120,4 +130,70 @@ export class ApplicationService {
});
});
}
gets(): Promise<ApplicationModel[]> {
return new Promise((resolve, reject) => {
this.http
.requestJson({
server: 'karso',
endPoint: 'application',
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
contentType: HTTPMimeType.JSON,
})
.then((response: ModelResponseHttp) => {
// TODO: check type ...
console.log(
`retreive return for application : get some data to check: ${JSON.stringify(response.data)}`
);
// tODO: check the format...
resolve(response.data);
})
.catch((error: any) => {
reject(`return ERROR ${JSON.stringify(error, null, 2)}`);
});
});
}
create(name: string, redirect: string): Promise<ApplicationModel> {
let body = {
name,
redirect,
};
return new Promise((resolve, reject) => {
this.http
.requestJson({
server: 'karso',
endPoint: 'application',
requestType: HTTPRequestModel.POST,
accept: HTTPMimeType.JSON,
contentType: HTTPMimeType.JSON,
body,
})
.then((response: ModelResponseHttp) => {
resolve(response.data);
})
.catch((error: any) => {
reject(`return ERROR ${JSON.stringify(error, null, 2)}`);
});
});
}
remove(id: number): Promise<void> {
return new Promise((resolve, reject) => {
this.http
.request({
server: 'karso',
endPoint: `application/${id}`,
requestType: HTTPRequestModel.DELETE,
accept: HTTPMimeType.ALL,
contentType: HTTPMimeType.JSON,
})
.then(() => {
resolve();
})
.catch((error: any) => {
reject(`return ERROR ${JSON.stringify(error, null, 2)}`);
});
});
}
}

View File

@ -1,5 +1,5 @@
import { AdminUserService } from './admin-user';
import { ApplicationService } from './application';
import { ApplicationModel, ApplicationService } from './application';
import { SettingsService } from './settings';
export { AdminUserService, ApplicationService, SettingsService };
export { AdminUserService, ApplicationService, SettingsService, ApplicationModel };

View File

@ -2,6 +2,7 @@ import { BddService } from './bdd';
import { CookiesService } from './cookies';
import { HttpWrapperService, ModelResponseHttp, HTTPRequest, HTTPMimeType, HTTPRequestModel } from './http-wrapper';
import { StorageService } from './local-storage';
import { NotificationService } from './notification';
import { PopInService } from './popin';
import {
OnlyAdminGuard,
@ -30,4 +31,5 @@ export {
OnlyUsersGuardHome,
OnlyUnregisteredGuardHome,
OnlyAdminGuard,
NotificationService,
};

View File

@ -0,0 +1,25 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Injectable } from '@angular/core';
@Injectable()
export class NotificationService {
constructor() {
console.log('Start Notification Service');
}
errorRaw(data: any) {
console.error(`get notification error: ${data}`)
}
warningRaw(data: any) {
console.info(`get notification warning: ${data}`)
}
infoRaw(data: any) {
console.info(`get notification info: ${data}`)
}
}

View File

@ -30,16 +30,17 @@ export class PopInService {
}
open(_id: string) {
//console.log("Try to open pop-in: '" + _id + "'");
console.log("Try to open pop-in: '" + _id + "'");
// open popin specified by id
for (let iii = 0; iii < this.popins.length; iii++) {
console.log(` check: ${this.popins[iii].id}`);
if (this.popins[iii].id === _id) {
//console.log(" ==>find it ...");
console.log(" ==>find it ...");
this.popins[iii].open();
return;
}
}
// console.log(" ==> NOT found !!!!!");
console.log(" ==> NOT found !!!!!");
}
close(_id: string) {

View File

@ -206,6 +206,39 @@ html {
vertical-align: middle;
}
}
.square-button {
// background: #b3d4fc;
text-shadow: none;
border: none;
border-radius: 2px;
position: relative;
height: 36px;
margin: 0;
width: 36px;
display: inline-block;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
//font-size: 14px;
//font-weight: 500;
text-transform: uppercase;
letter-spacing: 0;
overflow: hidden;
will-change: box-shadow;
outline: none;
cursor: pointer;
text-decoration: none;
text-align: center;
//line-height: 36px;
vertical-align: middle;
&:disabled {
cursor: not-allowed;
}
.material-icons {
vertical-align: middle;
}
}
.button-close {
// background: #b3d4fc;