Material design over ng-bootstrap

This commit is contained in:
ziajka 2017-10-31 14:44:47 +01:00
parent 805496c714
commit 6bfb4b5fed
14 changed files with 393 additions and 178 deletions

22
package-lock.json generated
View File

@ -46,9 +46,17 @@
} }
}, },
"@angular/animations": { "@angular/animations": {
"version": "4.3.3", "version": "4.4.6",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.3.3.tgz", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.4.6.tgz",
"integrity": "sha1-tx3dRTZzkp9VCxccypmVKzqqgxw=", "integrity": "sha1-+mYYmaik44y3xYPHpcl85l1ZKjU=",
"requires": {
"tslib": "1.7.1"
}
},
"@angular/cdk": {
"version": "2.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-2.0.0-beta.12.tgz",
"integrity": "sha1-OiQ8tiuT9OA5EgunD5ANyeI1Yi4=",
"requires": { "requires": {
"tslib": "1.7.1" "tslib": "1.7.1"
} }
@ -180,6 +188,14 @@
"integrity": "sha1-Y4z8oTQsOU9xP4wPo5jCYDLhVvQ=", "integrity": "sha1-Y4z8oTQsOU9xP4wPo5jCYDLhVvQ=",
"dev": true "dev": true
}, },
"@angular/material": {
"version": "2.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-2.0.0-beta.12.tgz",
"integrity": "sha1-cbbQt7AhiR5dDjaIwdS9eMdFf1g=",
"requires": {
"tslib": "1.7.1"
}
},
"@angular/platform-browser": { "@angular/platform-browser": {
"version": "4.3.3", "version": "4.3.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.3.3.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.3.3.tgz",

View File

@ -12,12 +12,14 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^4.0.0", "@angular/animations": "^4.4.6",
"@angular/cdk": "^2.0.0-beta.12",
"@angular/common": "^4.0.0", "@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0", "@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0", "@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0", "@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0", "@angular/http": "^4.0.0",
"@angular/material": "^2.0.0-beta.12",
"@angular/platform-browser": "^4.0.0", "@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0", "@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0", "@angular/router": "^4.0.0",

View File

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {Http} from "@angular/http";
import {MatIconRegistry} from "@angular/material";
import {DomSanitizer} from "@angular/platform-browser";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -6,6 +9,10 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor(http: Http, iconReg: MatIconRegistry, sanitizer: DomSanitizer) {
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
}
ngOnInit(): void { ngOnInit(): void {
} }
} }

View File

@ -11,7 +11,7 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { MapComponent } from './map/map.component'; import { MapComponent } from './map/map.component';
import { ProjectMapComponent } from './project-map/project-map.component'; import { ProjectMapComponent } from './project-map/project-map.component';
import { ServerCreateModalComponent, ServersComponent } from './servers/servers.component'; import { ServersComponent, AddServerDialogComponent } from './servers/servers.component';
import { ProjectsComponent } from './projects/projects.component'; import { ProjectsComponent } from './projects/projects.component';
import { VersionService } from './services/version.service'; import { VersionService } from './services/version.service';
@ -23,13 +23,28 @@ import { HttpServer } from "./services/http-server.service";
import { DefaultLayoutComponent } from './default-layout/default-layout.component'; import { DefaultLayoutComponent } from './default-layout/default-layout.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatButtonModule,
MatCardModule,
MatMenuModule,
MatToolbarModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatDialogModule
} from '@angular/material';
import {CdkTableModule} from "@angular/cdk/table";
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
MapComponent, MapComponent,
ProjectMapComponent, ProjectMapComponent,
ServersComponent, ServersComponent,
ServerCreateModalComponent, AddServerDialogComponent,
ProjectsComponent, ProjectsComponent,
DefaultLayoutComponent, DefaultLayoutComponent,
], ],
@ -38,7 +53,18 @@ import { DefaultLayoutComponent } from './default-layout/default-layout.componen
BrowserModule, BrowserModule,
HttpModule, HttpModule,
AppRoutingModule, AppRoutingModule,
FormsModule FormsModule,
BrowserAnimationsModule,
MatButtonModule,
MatMenuModule,
MatCardModule,
MatToolbarModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatDialogModule,
CdkTableModule
], ],
providers: [ providers: [
D3Service, D3Service,
@ -50,7 +76,7 @@ import { DefaultLayoutComponent } from './default-layout/default-layout.componen
HttpServer, HttpServer,
], ],
entryComponents: [ entryComponents: [
ServerCreateModalComponent, AddServerDialogComponent
], ],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })

View File

@ -0,0 +1,53 @@
html, body {
height: 100%;
}
app-root, app-default-layout {
height: 100%;
}
app-default-layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.content {
flex: 1 0 auto;
}
.footer {
flex-shrink: 0;
padding: 20px;
background-color: #e91e63;
color: white;
}
.default-content {
margin: 0 auto;
max-width: 940px;
padding-top: 20px;
padding-bottom: 20px;
}
main {
height: 100%;
}
.default-header h1 {
font-weight: 300;
margin: 0;
font-size: 20px;
padding: 28px 8px;
color: white;
}
.default-header {
padding-left: 20px;
background-color: #e91e63;
}
.buttons-bar {
padding-top: 10px;
text-align: right;
}

View File

@ -1,19 +1,18 @@
<nav class="navbar navbar-expand-lg navbar-dark"> <header>
<a class="navbar-brand" href="/"><img src="assets/logo-header.png"></a> <mat-toolbar color="primary">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <button mat-icon-button>
<span class="navbar-toggler-icon"></span> <mat-icon svgIcon="gns3"></mat-icon>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" [routerLink]="['/servers']" >Servers <span class="sr-only">(current)</span></a>
</li>
</ul>
</div>
</nav>
<a mat-button routerLink="/servers" >Servers</a>
</mat-toolbar>
</header>
<main class="mat-app-background">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</main>
<footer class="footer"> <footer class="footer primary-light-bg">
<span class="text-muted">GNS3 Web UI demo</span> GNS3 Web UI demo &copy; 2017
</footer> </footer>

View File

@ -1,15 +1,15 @@
html { /*html {*/
position: static; /*position: static;*/
height: 100%; /*height: 100%;*/
} /*}*/
body { /*body {*/
height: 100%; /*height: 100%;*/
margin: 0; /*margin: 0;*/
margin-bottom: 0 !important; /*margin-bottom: 0 !important;*/
} /*}*/
app-root, app-project-map, .project-map, app-map, svg { /*app-root, app-project-map, .project-map, app-map, svg {*/
height: 100%; /*height: 100%;*/
} /*}*/

View File

@ -0,0 +1,16 @@
<h1 mat-dialog-title>Add server</h1>
<div mat-dialog-content>
<mat-form-field>
<input matInput tabindex="1" [(ngModel)]="server.name" placeholder="Name">
</mat-form-field>
<mat-form-field>
<input matInput tabindex="1" [(ngModel)]="server.ip" placeholder="IP">
</mat-form-field>
<mat-form-field>
<input matInput tabindex="1" [(ngModel)]="server.port" placeholder="Port">
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onAddClick()" tabindex="2">Add</button>
<button mat-button (click)="onNoClick()" tabindex="-1">No Thanks</button>
</div>

View File

@ -1,27 +0,0 @@
<div class="modal-header">
<h4 class="modal-title">Add server</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form #f="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="Enter name" [(ngModel)]="server.name" required>
</div>
<div class="form-group">
<label for="ip">IP</label>
<input type="text" class="form-control" id="ip" name="ip" placeholder="Enter IP" [(ngModel)]="server.ip" required>
</div>
<div class="form-group">
<label for="port">Port</label>
<input type="number" class="form-control" id="port" name="port" placeholder="Enter Port" [(ngModel)]="server.port" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.dismiss()">Close</button>
<button type="button" class="btn btn-success" (click)="add()" [disabled]="!f.valid">Add</button>
</div>

View File

@ -1,29 +1,49 @@
<div class="container page"> <div class="content">
<div class="default-header">
<h1>Servers</h1> <h1>Servers</h1>
</div>
<div class="default-content">
<div class="row"> <div class="example-container mat-elevation-z8">
<table class="table table-inverse"> <mat-table #table [dataSource]="dataSource">
<thead>
<tr> <ng-container matColumnDef="id">
<th>#</th> <mat-header-cell *matHeaderCellDef> ID </mat-header-cell>
<th>Name</th> <mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
<th>IP:Port</th> </ng-container>
<th>Action</th>
</tr> <ng-container matColumnDef="name">
</thead> <mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
<tbody> <mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
<tr *ngFor="let server of servers"> </ng-container>
<th scope="row">{{ server.id }}</th>
<td><a [routerLink]="['/server', server.id, 'projects']">{{ server.name }}</a></td> <ng-container matColumnDef="ip">
<td>{{ server.ip }}:{{ server.port }}</td> <mat-header-cell *matHeaderCellDef> IP </mat-header-cell>
<td><button class="btn btn-outline-danger btn-sm " (click)="deleteServer(server)">Remove</button></td> <mat-cell *matCellDef="let row"> {{row.ip}} </mat-cell>
</tr> </ng-container>
</tbody>
</table> <ng-container matColumnDef="port">
<mat-header-cell *matHeaderCellDef> Port </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.port}} </mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef> Actions </mat-header-cell>
<mat-cell *matCellDef="let row">
<button mat-icon-button (click)="deleteServer(row)">
<mat-icon aria-label="Remove server">delete</mat-icon>
</button>
</mat-cell>
</ng-container>
>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div> </div>
<div class="row"> <div class="buttons-bar">
<button class="btn btn-primary btn-lg active" (click)="createModal()">Add server</button> <button mat-raised-button color="primary" (click)="createModal()">Add server</button>
</div>
</div> </div>
</div> </div>

View File

@ -1,23 +1,18 @@
import { Component, OnInit } from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Server } from "../models/server"; import { Server } from "../models/server";
import { ServerService } from "../services/server.service"; import { ServerService } from "../services/server.service";
import {DataSource} from "@angular/cdk/collections";
import {Observable} from "rxjs/Observable";
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import 'rxjs/add/operator/startWith';
@Component({ import 'rxjs/add/observable/merge';
selector: 'app-server-create-modal', import 'rxjs/add/operator/map';
templateUrl: './server-create-modal.component.html' import 'rxjs/add/operator/debounceTime';
}) import 'rxjs/add/operator/distinctUntilChanged';
export class ServerCreateModalComponent { import 'rxjs/add/observable/fromEvent';
public server = new Server();
constructor(public activeModal: NgbActiveModal) {}
add() {
this.activeModal.close(this.server);
}
}
@Component({ @Component({
@ -27,32 +22,101 @@ export class ServerCreateModalComponent {
}) })
export class ServersComponent implements OnInit { export class ServersComponent implements OnInit {
servers: Server[] = []; servers: Server[] = [];
serverDatabase = new ServerDatabase();
dataSource: ServerDataSource;
displayedColumns = ['id', 'name', 'ip', 'port', 'actions'];
constructor(private modalService: NgbModal, private serverService: ServerService) { } constructor(private dialog: MatDialog, private serverService: ServerService) {}
ngOnInit() { ngOnInit() {
this.loadServers();
}
loadServers() {
this.serverService.findAll().then((servers: Server[]) => { this.serverService.findAll().then((servers: Server[]) => {
this.servers = servers; this.serverDatabase.addServers(servers);
}); });
this.dataSource = new ServerDataSource(this.serverDatabase);
} }
createModal() { createModal() {
this.modalService.open(ServerCreateModalComponent).result.then((server: Server) => { const dialogRef = this.dialog.open(AddServerDialogComponent, {
this.serverService.create(server).then((created: Server) => { width: '250px',
this.loadServers();
}); });
}, (rejection) => {
dialogRef.afterClosed().subscribe(server => {
if (server) {
this.serverService.create(server).then((created: Server) => {
this.serverDatabase.addServer(created);
});
}
}); });
} }
deleteServer(server: Server) { deleteServer(server: Server) {
this.serverService.delete(server).then(() => { this.serverService.delete(server).then(() => {
this.loadServers(); this.serverDatabase.remove(server);
}); });
} }
} }
@Component({
selector: 'app-add-server-dialog',
templateUrl: 'add-server-dialog.html',
})
export class AddServerDialogComponent {
server: Server = new Server();
constructor(
public dialogRef: MatDialogRef<AddServerDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, serverService: ServerService) {
}
onAddClick(): void {
this.dialogRef.close(this.server);
}
onNoClick(): void {
this.dialogRef.close();
}
}
export class ServerDatabase {
dataChange: BehaviorSubject<Server[]> = new BehaviorSubject<Server[]>([]);
get data(): Server[] {
return this.dataChange.value;
}
public addServer(server: Server) {
const servers = this.data.slice();
servers.push(server);
this.dataChange.next(servers);
}
public addServers(servers: Server[]) {
this.dataChange.next(servers);
}
public remove(server: Server) {
const index = this.data.indexOf(server);
if (index >= 0) {
this.data.splice(index, 1);
this.dataChange.next(this.data.slice());
}
}
}
export class ServerDataSource extends DataSource<any> {
constructor(private serverDatabase: ServerDatabase) {
super();
}
connect(): Observable<Server[]> {
return Observable.merge(this.serverDatabase.dataChange).map(() => {
return this.serverDatabase.data;
});
}
disconnect() {}
}

View File

@ -22,8 +22,15 @@ export class ServerService {
} }
public create(server: Server) { public create(server: Server) {
return this.onReady(() => return this.onReady(() => {
this.indexedDbService.get().add(this.tablename, server)); const promise = new Promise((resolve, reject) => {
this.indexedDbService.get().add(this.tablename, server).then((added) => {
server.id = added.key;
resolve(server);
}, reject);
});
return promise;
});
} }
public findAll() { public findAll() {

88
src/assets/gns3_icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,63 +1,7 @@
html { /* @TODO: make icons self hosted */
position: relative; @import '~https://fonts.googleapis.com/icon?family=Material+Icons';
min-height: 100%; @import '~@angular/material/prebuilt-themes/pink-bluegrey.css';
}
body { img.logo-header {
margin-bottom: 60px;
background-color: #26353f;
font-family: Roboto;
height: 100%;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px;
line-height: 60px;
}
.navbar-brand > img {
width: 50px; width: 50px;
} }
.page h1 {
margin-top: 20px;
margin-bottom: 20px;
color: white;
}
.footer {
padding-right: 15px;
padding-left: 15px;
}
.table-inverse {
background-color: #26353f;
}
.table-inverse {
background-color: #26353f;
}
.table-inverse thead th {
border: none;
}
.table thead th {
border-bottom: 2px solid white;
}
.table td, .table th {
border-top: 1px solid white;
}
.project-map {
text-align: center;
}
.project-map svg {
background-color: lightgray;
}