Skip to content

Commit

Permalink
ui-core: initial UCI functionality
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Panella <[email protected]>
  • Loading branch information
ianchi committed Aug 10, 2017
1 parent 5184ce3 commit 759976d
Show file tree
Hide file tree
Showing 22 changed files with 581 additions and 2 deletions.
18 changes: 16 additions & 2 deletions luci2-ui-core/src/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ import { Routes } from '@angular/router/router';
import { AppComponent } from './app.component';
import { StatusComponent } from './plugins/status/status.component';
import { UbusViewerComponent } from './plugins/ubusViewer/ubusViewer.component';
import { UciEditorComponent } from './plugins/uciEditor/uciEditor.component';
import { JsonrpcService } from './shared/jsonrpc.service';
import { MenuService } from './shell/menu/menu.service';
import { ShellModule } from './shell/shell.module';
import { UbusDirective } from './ubus/ubus.directive';
import { UbusService } from './ubus/ubus.service';
import { UciConfigComponent } from './uci/components/uciConfig/uciConfig.component';
import { UciFormComponent } from './uci/components/uciForm/uciForm.component';
import { UciOptionComponent } from './uci/components/uciOption/uciOption.component';
import { UciSectionComponent } from './uci/components/uciSection/uciSection.component';
import { UciService } from './uci/uci.service';
import { UciModelService } from './uci/uciModel.service';
import { StatsComponent } from './widgets/stats/stats.component';

const routes: Routes = [
{path: 'status', component: StatusComponent},
{path: 'system', component: UbusViewerComponent},
{path: 'network', component: UciEditorComponent},
{ path: '', redirectTo: '/status', pathMatch: 'full'},
];

Expand All @@ -33,7 +41,13 @@ const routes: Routes = [

StatusComponent,
UbusViewerComponent,
StatsComponent
StatsComponent,
UciEditorComponent,

UciOptionComponent,
UciSectionComponent,
UciConfigComponent,
UciFormComponent
],
imports: [
BrowserModule,
Expand All @@ -42,7 +56,7 @@ const routes: Routes = [
ShellModule,
RouterModule.forRoot(routes),
],
providers: [ JsonrpcService, UbusService, MenuService ],
providers: [ JsonrpcService, UbusService, MenuService, UciService, UciModelService ],
bootstrap: [AppComponent]
})
export class AppModule { }
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

<md-select placeholder="Config file" [(ngModel)]="selectedConfig" name="config" *appUbus="['uci', 'configs']; let configs">
<md-option *ngFor="let config of configs.configs" [value]="config">{{config}}</md-option>

</md-select>

<uci-form [configName]="selectedConfig"></uci-form>

17 changes: 17 additions & 0 deletions luci2-ui-core/src/src/app/plugins/uciEditor/uciEditor.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-uci-editor',
templateUrl: './uciEditor.component.html',
styleUrls: ['./uciEditor.component.css']
})
export class UciEditorComponent implements OnInit {
public selectedConfig: string;


constructor() { }

ngOnInit() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<form novalidate>
<md-tab-group>
<md-tab *ngFor="let type of config.types" label="{{type[0]}}">
<md-card *ngFor="let section of type[1]" >
<md-card-title>{{section.name}}</md-card-title>
<uci-section [section]="section"></uci-section>
</md-card>

</md-tab>
</md-tab-group>

</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import './../../../../styles/_components.scss';


uci-config {
.mat-card {
margin: $unit;
margin-top: $unit * 2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { Config } from '../../config';

import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
selector: 'uci-config',
templateUrl: './uciConfig.component.html',
styleUrls: ['./uciConfig.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class UciConfigComponent implements OnInit {

@Input() config: Config;

constructor() { }

ngOnInit() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<form novalidate>

<uci-config *ngIf="isLoaded" [config]="config">

</uci-config>

<div class="uci-form-actions">
<button md-button>Save</button>
<button md-button>Save & Apply</button>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './../../../../styles/_components.scss';

.uci-form-actions {
margin-top: $unit * 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { Config } from '../../config';
import { UciModelService } from '../../uciModel.service';

import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';

@Component({
selector: 'uci-form',
templateUrl: './uciForm.component.html',
styleUrls: ['./uciForm.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class UciFormComponent implements OnInit, OnChanges {
@Input() configName: string;

public isLoaded = false;
public config: Config;

constructor(public uciModel: UciModelService) { }

ngOnInit() {
this.load();
}
ngOnChanges(changes: SimpleChanges) {
if (!changes.configName.firstChange)
this.load();
}

load() {
if (this.configName) {
this.uciModel.loadConfig(this.configName)
.subscribe(c => { this.isLoaded = true; this.config = c; });

}

}

}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<md-input-container>
<input mdInput [placeholder]="option.title" [(ngModel)]="option.value" type="option.inputType">
</md-input-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { Option } from '../../option';

import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';



/**
* uciOption: renders an uci Option object using the correct input control
*/
@Component({
selector: 'uci-option',
templateUrl: './uciOption.component.html',
styleUrls: ['./uciOption.component.css'],
encapsulation: ViewEncapsulation.None
})
export class UciOptionComponent implements OnInit {

@Input() option: Option;


constructor() { }

ngOnInit() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<form novalidate>
<uci-option *ngFor="let option of section.options" [option]="option"></uci-option>

</form>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { Section } from '../../section';

import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
selector: 'uci-section',
templateUrl: './uciSection.component.html',
styleUrls: ['./uciSection.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class UciSectionComponent implements OnInit {

@Input() section: Section;

constructor() { }

ngOnInit() {
}

}
48 changes: 48 additions & 0 deletions luci2-ui-core/src/src/app/uci/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { IUciConfig, IUciConfigSchema } from './uci.interface';
import { Section } from './section';

export class Config {
title: string;
description: string;
info?: string;

types: [string, Section[]][] = [];


constructor(public name: string, uci: IUciConfig, public schema?: IUciConfigSchema) {
let sections: Section[];

if (typeof schema === 'object') {
this.title = schema.title || name;
this.description = schema.description || '';
this.info = schema.info || '';
if (!Array.isArray(schema.sections)) schema.sections = [];
} else
schema = <IUciConfigSchema> { sections: []};

for (const sec of Object.keys(uci)) {
sections = this.getSections(uci[sec]['.type']);
if (!sections) {
sections = [];
this.types.push([uci[sec]['.type'], sections]); }


sections.push(new Section(uci[sec], schema.sections.find( s => s.type === uci[sec]['.type'])));
}
}

getSections(type: string): Section[] {
const sections = this.types.find(e => e[0] === type);
return sections && sections[1] || null;
}

getTypes() {
return this.types;
}

}
96 changes: 96 additions & 0 deletions luci2-ui-core/src/src/app/uci/option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*!
* Copyright (c) 2017 Adrian Panella <[email protected]>, contributors.
* Licensed under the MIT license.
*/

import { IUciOptionSchema, UciActions } from './uci.interface';


/**
* Option: object that models an `uci option` with its schema information.
* It keeps tracks of the changes so it can later inform a service that updates the backend
*/
export class Option implements IUciOptionSchema {
static types = {
bool: 'checkbox',
integer: 'text',
uinteger: 'text',
float: 'text',
ufloat: 'text',
string: 'text',
ipaddr: 'text',
ip4addr: 'text',
ip6addr: 'text',
netmask4: 'text',
netmask6: 'text',
cidr4: 'text',
cidr6: 'text',
ipmask4: 'text',
ipmask6: 'text',
port: 'text',
portrange: 'text',
macaddr: 'text',
host: 'text',
hostname: 'text',
wpakey: 'text',
wepkey: 'text',
device: 'cbiDeviceList',
network: 'cbiNetworkList'
};

oldValue: string | string[];
action = UciActions.Add;

title: string;
description: string;

type = 'string';
isList = false;
required = false;
validation: string;
values: string[];


constructor(public name: string, public value: string | string[], public schema?: IUciOptionSchema) {


if (typeof schema === 'object') {
this.type = schema.type || 'string';
this.required = !!schema.required;
this.validation = schema.validation;
this.title = schema.title || name;
this.description = schema.description;
if (schema.isList && !Array.isArray(value)) value = [value];
if (Array.isArray(schema.values)) this.values = schema.values;
} else {
this.title = name;
}

if (Array.isArray(value)) {
this.isList = true;
this.oldValue = value.slice(0);
} else if (typeof value === 'object') {
throw new Error('Option can only be assigned primitives or array');
} else {
this.oldValue = value;
}
}

delete() {
this.action = UciActions.Delete;
}

get isModified(): boolean {
if (this.action === UciActions.Delete) return false;
if (this.action === UciActions.Add) return true;

if (typeof this.value !== 'string' && this.isList)
return (this.value.length === this.oldValue.length) &&
this.value.every((element, index) => element === this.oldValue[index]);
return this.value === this.oldValue;
}

get inputType(): string {
return Option.types[this.type] || 'text';
}
}
Loading

0 comments on commit 759976d

Please sign in to comment.