Skip to content

Commit

Permalink
Fix #39 Final changes to support loading a remote JSON config file. R…
Browse files Browse the repository at this point in the history
…ead the documentation to learn how it works: https://github.com/ligreman/king#configuration
  • Loading branch information
ligreman committed Jul 29, 2023
1 parent 84428fb commit 43b997b
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 53 deletions.
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,44 @@ application server ([Apache](https://httpd.apache.org/download.cgi), [Nginx](htt

King for Kong is an Angular web application without a backend. This is a limitation when you want to have configuration
parameters persisted among sessions. To cover part of this feature you can have a JSON config file served by an
application server (apache, nginx... maybe the same that is serving King) as a static file. Then you can set in King
the "Config File Url" parameter to point to that config file, and King will load it on start.
application server (apache, nginx... maybe the same server serving King) as a static file. Then you can set in King
the url to access this configuration file, and King will load it on start.

Steps to set this up:

1) Create a JSON file with the configuration desired (you have a template in the Examples folder)
2) Serve it with your web server as an static file. For improved security make sure that only the web server is able to
access the file.
3) Open the settings dialog (gear icon on the left of the Kong nodw url field in the header).
4) Set the config file url up.
4) Type the config file url.

Fields allowed in the config file:
Note: If no config url is set, King by default will look for the config file at <king url>/config.json. Example: if you are accessing King at http://king.com:8080, King will look for the file at http://king.com:8080/config.json.

* kongNodeUrl: used if you want to set a default kong url node.
Field description of the config file:

* kongNodeUrl: default kong url node to connect to.

### King Globals configuration

Some parameters can be set at code level (they are hardcoded in the source files). You can change them to change King's default behaviour to a certain level.

The file containing the global configuration is located at `src/app/services/globals.service.ts`

There you have these variables:

```
// Default url for the configuration JSON file
private _CONFIG_URL = '/config.json';
// Enable or disable the config dialog in the interface
private _ALLOW_CONFIG = false;
// Allow users to change the url of the Kong node to connect to
private _ALLOW_CHANGE_KONG_URL = true;
// Allow users to change the url of the JSON configuration file to load
private _ALLOW_CHANGE_CONFIG_FILE_URL = true;
```

## Development

Expand Down
2 changes: 1 addition & 1 deletion examples/config.json.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"kongNodeUrl": "http://patata.com"
"kongNodeUrl": "http://foo-hostname.com"
}
9 changes: 6 additions & 3 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@
<span fxFlex></span>

<form [formGroup]="formNodes" fxFlex.gt-md="25" fxFlex.lt-lg="40">
<button (click)="connectSettings()" [matTooltipShowDelay]="1000" color="accent"
<button (click)="settingsDialog()" [matTooltipShowDelay]="1000" color="accent"
mat-icon-button matTooltipClass="tooltip-teal"
matTooltip="{{'header.menu.settings_tooltip' | translate}}">
matTooltip="{{'header.menu.settings_tooltip' | translate}}"
*ngIf="allowConfig">
<mat-icon>settings</mat-icon>
</button>
<mat-form-field class="small-font" color="accent" fxFlex>
Expand All @@ -110,6 +111,7 @@
matTooltip="{{'header.menu.kong_node_tooltip' | translate}}"
matTooltipClass="tooltip-teal"
placeholder="{{'header.menu.kong_node' | translate}}"
[readonly]="!enableKongUrl"
type="text"/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of node_list" [value]="option" class="small-font">
Expand All @@ -119,7 +121,8 @@
<mat-error *ngIf="nodeField.invalid && (nodeField.dirty || nodeField.touched)">
<div *ngIf="nodeField.errors.required">{{ 'header.menu.error_node_field' | translate }}</div>
</mat-error>
<mat-hint>{{'header.menu.kong_node_hint' | translate}}</mat-hint>
<mat-hint *ngIf="!enableKongUrl">{{'header.menu.kong_node_disabled_hint' | translate}}</mat-hint>
<mat-hint *ngIf="enableKongUrl">{{'header.menu.kong_node_hint' | translate}}</mat-hint>
</mat-form-field>
<button (click)="connectToNode()" [matTooltipShowDelay]="1000" color="accent"
mat-icon-button
Expand Down
54 changes: 32 additions & 22 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ export class AppComponent implements OnInit, OnDestroy {
lang = new FormControl('');
openedManual = false;
showManualText = false;
enableKongUrl = true;
manualStyles = {'height': 0, 'top': 0};
enabledPlugins = [];
allowConfig = false;

formNodes = this.fb.group({
node: ['', Validators.required]
Expand Down Expand Up @@ -73,49 +75,58 @@ export class AppComponent implements OnInit, OnDestroy {
this.toast.error('error.load_loopback')
}
}

this.allowConfig = this.globals.ALLOW_CONFIG;
}

ngOnDestroy(): void {
}

ngOnInit(): void {
this.getConfig();
}

// GETTERS
get nodeField() {
return this.formNodes.get('node');
}

ngOnInit(): void {
getConfig() {
const confFile = localStorage.getItem('kongConfigFileUrl');
if (confFile !== undefined && confFile !== null && confFile !== '') {
this.globals.CONFIG_URL = confFile;
}

// Get config
this.enableKongUrl = this.globals.ALLOW_CHANGE_KONG_URL;

this.loadConfig().then(value => {
if (value !== null) {
this.globals.NODE_API_URL = value['kongNodeUrl'];
}

// TODO si está loopback activo, abro la pantalla de setting para que metas las credenciales y conectes
// Conecto al nodo y si no lo consigo voy a landing

// I connect to the node and if I don't get it I'm going to landing
if (this.globals.NODE_API_URL !== '' && this.globals.ROUTER_MODE === '') {
this.api.getNodeInformation()
.subscribe({
next: (res) => {
this.globals.ROUTER_MODE = res['configuration']['router_flavor'];
this.formNodes.setValue({node: this.globals.NODE_API_URL});
this.connectToNode();
}, error: () => this.route.navigate(['/landing'])
});
}
});
}

ngOnDestroy(): void {
}

async loadConfig() {
let config = null, cfg = undefined;
try {
if (this.globals.CONFIG_URL !== '') {
cfg = await firstValueFrom(this.api.getConfig());
cfg = await firstValueFrom(this.api.getConfiguration());
}
} catch (e) {
console.error('Error loading config file from ' + this.globals.CONFIG_URL);
}

// If not found online, try to get local
Expand All @@ -133,63 +144,63 @@ export class AppComponent implements OnInit, OnDestroy {
return config;
}

connectSettings() {
settingsDialog() {
const dialogRef = this.dialog.open(DialogSettingsComponent, {
minWidth: '80vw',
minHeight: '50vh',
data: {}
});
dialogRef.afterClosed().subscribe(result => {
if (result !== null && result !== 'null') {
} else {
this.getConfig();
}
});
}

/**
Conecta al nodo de Kong, pidiendo su información básica
Connects to Kong's node, asking for his basic information
*/
connectToNode() {
if (this.formNodes.invalid) {
return;
}

// Recojo el valor
// Collect the value
let node = this.nodeField.value;
node = node.replace(new RegExp('/$'), '');

// Guardo el nodo como valor para conectar
// save node as value to connect
this.globals.NODE_API_URL = node;

// Conecto al nodo de Kong elegido
// Connect to the chosen Kong node
this.api.getNodeInformation()
.subscribe({
next: (res) => {
// Si ha ido bien la conexión guardo como nodo esta url
// If the connection went well, I save this url as a node
if (!this.node_list.includes(node)) {
this.node_list.push(node);
localStorage.setItem('kongNodes', btoa(this.node_list.join(',')));
}
localStorage.setItem('kongLastNode', btoa(node));
this.toast.success('header.node_connected', '', {msgExtra: node});

// Aviso del cambio de nodo
// Notice of node change
this.nodeWatcher.changeNode(node);

// Recojo los plugins activos para habilitar las secciones
// collect the active plugins to enable the sections
this.enabledPlugins = res['plugins']['enabled_in_cluster'];

// Modo del router
// ROUTER MODE
this.globals.ROUTER_MODE = res['configuration']['router_flavor'];

// Voy a la página de información de nodos
// go to the node information page
this.route.navigate(['/node-information']);
}, error: (error) => this.toast.error('error.node_connection')
});
}

/**
Cambia el idioma
Change the language
*/
changeLang(newLang: string) {
this.lang.setValue(newLang);
Expand All @@ -198,10 +209,9 @@ export class AppComponent implements OnInit, OnDestroy {
}

/**
Muestra u oculta el manual de usuario
Show or hide the user manual
*/
toggleManual() {
// Si voy a mostrar el manual
if (!this.openedManual) {
this.openedManual = !this.openedManual;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ <h2 class="margin-bottom-5-i" fxLayout="row" fxLayoutAlign="space-between start"

<form [formGroup]="form" class="padding-30">
<div class="margin-bottom-10" fxLayout="column">
<div class="margin-bottom-10" fxLayout="row" fxLayoutAlign="space-between center">
<div class="margin-bottom-10" fxLayout="row" fxLayoutAlign="space-between center" *ngIf="allowChangeConfigFile">
<mat-form-field appearance="outline" color="accent" fxFlex="75">
<mat-label>{{'dialog.settings.config_url' | translate}}</mat-label>
<input formControlName="config_url" matInput type="text"
[readonly]="!allowChangeConfigFile"
placeholder="https://host:8443/config.json"/>
<mat-hint>
<mat-hint *ngIf="allowChangeConfigFile">
<mat-error *ngIf="form.hasError('configUrlValidation'); else hintAlone">
{{ 'dialog.settings.error.config_url' | translate }}
</mat-error>
Expand All @@ -32,6 +33,9 @@ <h2 class="margin-bottom-5-i" fxLayout="row" fxLayoutAlign="space-between start"
{{'dialog.settings.documentation' | translate}}
<mat-icon class="mini-icon">open_in_new</mat-icon></a></span>
</div>
<div>
<p *ngIf="!allowChangeConfigFile">{{ 'dialog.settings.cannot_change_config_url_hint' | translate }}: {{config_file_url}}</p>
</div>
</div>
</form>
</mat-tab>
Expand Down
38 changes: 22 additions & 16 deletions src/app/components/dialog-settings/dialog-settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ApiService} from '../../services/api.service';
import {ToastService} from '../../services/toast.service';
import * as Joi from "joi";
import {CustomValidators} from "../../shared/custom-validators";
import {GlobalsService} from "../../services/globals.service";

@AutoUnsubscribe()
@Component({
Expand All @@ -15,21 +16,22 @@ import {CustomValidators} from "../../shared/custom-validators";
})
export class DialogSettingsComponent implements OnInit, OnDestroy {
formValid = false;
editMode = false;
allowChangeConfigFile = true;
config_file_url = '';

form = this.fb.group({
config_url: ['', []]
}, {validators: [ConfigFormValidator()]});

formLoop = this.fb.group({
enabled: ['false', [CustomValidators.isBoolean(false)]],
enabled: [false, [CustomValidators.isBoolean(false)]],
where: ['', [Validators.required]],
key_field: ['', [Validators.required]],
token: ['', [Validators.required]]
}, {});

constructor(@Inject(MAT_DIALOG_DATA) public consumerIdEdit: any, private fb: FormBuilder, public dialogRef: MatDialogRef<DialogSettingsComponent>,
private api: ApiService, private toast: ToastService) {
private api: ApiService, private toast: ToastService, private globals: GlobalsService,) {
/* TODO
check enable or not
where: header, query, body
Expand All @@ -50,11 +52,14 @@ export class DialogSettingsComponent implements OnInit, OnDestroy {


ngOnInit(): void {
const confFile = localStorage.getItem('kongConfigFileUrl');
if (confFile !== null && confFile !== undefined) {
this.form.setValue({config_url: confFile});
// Config file URL
let confFileUrl = localStorage.getItem('kongConfigFileUrl');
if (confFileUrl === null || confFileUrl === undefined) {
confFileUrl = this.globals.CONFIG_URL;
}

this.config_file_url = confFileUrl;
this.form.setValue({config_url: confFileUrl});
this.allowChangeConfigFile = this.globals.ALLOW_CHANGE_CONFIG_FILE_URL;
}

ngOnDestroy(): void {
Expand All @@ -64,12 +69,13 @@ export class DialogSettingsComponent implements OnInit, OnDestroy {
Submit del formulario
*/
onSubmit() {
// CONFIG
localStorage.setItem('kongConfigFileUrl', this.form.value.config_url);

// LOOPBACK
// If i can save the config file url
if (this.allowChangeConfigFile) {
// CONFIG
localStorage.setItem('kongConfigFileUrl', this.form.value.config_url);
}

this.toast.success('dialog.settings.saved', '');
this.toast.success('dialog.settings.saved', '', {timeout:5000});
this.dialogRef.close();
}
}
Expand All @@ -81,11 +87,11 @@ export class DialogSettingsComponent implements OnInit, OnDestroy {
function ConfigFormValidator(): ValidatorFn {
return (fg: AbstractControl): ValidationErrors => {
let valid = true;
let data = {configUrlValidation: false};
let data = {configUrlValidation: false, confKongAllow: false};

const schema = Joi.string().uri().allow('');
const {error, value} = schema.validate(fg.get('config_url').value);
if (error) {
const schema = Joi.string().uri().allow('').allow('/config.json');
const validation = schema.validate(fg.get('config_url').value);
if (validation.error) {
valid = false;
data.configUrlValidation = true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class ApiService {
/*
GENERAL ENDPOINTS
*/
public getConfig() {
public getConfiguration() {
return this.httpClient.get(this.globals.CONFIG_URL).pipe(catchError(this.handleError));
}

Expand Down
Loading

0 comments on commit 43b997b

Please sign in to comment.