gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[reclaim-ui] 02/459: Migrated from internal devops without history


From: gnunet
Subject: [reclaim-ui] 02/459: Migrated from internal devops without history
Date: Fri, 11 Jun 2021 23:21:34 +0200

This is an automated email from the git hooks/post-receive script.

martin-schanzenbach pushed a commit to branch master
in repository reclaim-ui.

commit 253233a36dc736070c5ab870e3c36d8133b0a00c
Author: Schanzenbach, Martin <mschanzenbach@posteo.de>
AuthorDate: Tue Nov 20 15:23:41 2018 +0100

    Migrated from internal devops without history
---
 Dockerfile                                         |  35 ++
 docker-entrypoint.sh                               |  12 +
 package.json                                       |  41 ++
 proxy.conf.json                                    |  10 +
 src/app/app-routing.module.ts                      |  14 +
 src/app/app.component.html                         |   2 +
 src/app/app.component.scss                         |  19 +
 src/app/app.component.ts                           |  10 +
 src/app/app.module.ts                              |  44 +++
 src/app/attribute.ts                               |   5 +
 src/app/config.service.ts                          |  19 +
 src/app/config.ts                                  |   3 +
 src/app/gns-response.ts                            |   3 +
 src/app/gns.service.ts                             |  17 +
 src/app/gnu-net-response.ts                        |   3 +
 src/app/identity-list/identity-list.component.html | 214 ++++++++++
 src/app/identity-list/identity-list.component.scss |  46 +++
 src/app/identity-list/identity-list.component.ts   | 435 +++++++++++++++++++++
 src/app/identity.service.ts                        |  29 ++
 src/app/identity.ts                                |   4 +
 src/app/namestore-response.ts                      |   3 +
 src/app/namestore.service.ts                       |  17 +
 src/app/open-id.service.ts                         |  52 +++
 src/app/reclaim.service.ts                         |  34 ++
 src/app/record.ts                                  |   5 +
 src/app/rxjs.ts                                    |   1 +
 src/app/ticket.ts                                  |   5 +
 src/assets/Audiowide-Regular.ttf                   | Bin 0 -> 69916 bytes
 src/assets/config.json                             |   3 +
 src/assets/reclaim_icon.png                        | Bin 0 -> 7834 bytes
 src/assets/reclaim_small.png                       | Bin 0 -> 65046 bytes
 src/environments/environment.prod.ts               |   3 +
 src/environments/environment.ts                    |   8 +
 src/index.html                                     |  17 +
 src/main.ts                                        |  12 +
 src/polyfills.ts                                   |  79 ++++
 src/styles.scss                                    |  57 +++
 src/tsconfig.app.json                              |  13 +
 src/typings.d.ts                                   |   5 +
 tsconfig.json                                      |  19 +
 tslint.json                                        | 154 ++++++++
 41 files changed, 1452 insertions(+)

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7814a8d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,35 @@
+FROM node:8-alpine AS build
+
+LABEL maintainer="Christian Banse <christian.banse@aisec.fraunhofer.de>"
+
+EXPOSE 80
+
+WORKDIR /tmp
+
+# this should hopefully trigger Docker to only update yarn if dependencies 
have changed
+ADD *.json ./
+ADD *.lock ./
+RUN yarn install
+
+# add the rest of the files
+ADD . .
+
+# set environment to production
+ENV NODE_ENV production
+
+# lint
+#RUN yarn lint
+
+# build everything for production
+RUN yarn run build --prod
+
+FROM nginx:alpine
+
+# copy to nginx
+COPY --from=build /tmp/dist /usr/share/nginx/html/
+
+ADD ./docker-entrypoint.sh /
+ENTRYPOINT ["/bin/sh", "/docker-entrypoint.sh"]
+
+EXPOSE 80
+CMD ["nginx"]
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100644
index 0000000..b55d7d7
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,12 @@
+#/bin/bash
+set -a
+
+if [ -n "$API_URL" ]; then
+  echo {\"apiUrl\": \"${API_URL}\"} > /usr/share/nginx/html/assets/config.json
+fi
+
+if [ "$1"  = 'nginx' ]; then
+  exec nginx -g "daemon off;"
+fi
+
+exec "$@"
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9d30681
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+  "name": "reclaim-ui",
+  "version": "0.0.0",
+  "license": "MIT",
+  "scripts": {
+    "ng": "ng",
+    "start": "ng serve --proxy-config proxy.conf.json",
+    "build": "ng build --prod",
+    "test": "ng test",
+    "lint": "ng lint",
+    "e2e": "ng e2e"
+  },
+  "private": true,
+  "dependencies": {
+    "@angular/animations": "^5.2.0",
+    "@angular/common": "^5.2.0",
+    "@angular/compiler": "^5.2.0",
+    "@angular/core": "^5.2.0",
+    "@angular/forms": "^5.2.0",
+    "@angular/http": "^5.2.0",
+    "@angular/platform-browser": "^5.2.0",
+    "@angular/platform-browser-dynamic": "^5.2.0",
+    "@angular/router": "^5.2.0",
+    "bootstrap": "4",
+    "core-js": "^2.4.1",
+    "font-awesome": "^4.7.0",
+    "jquery": "^3.3.1",
+    "popper.js": "^1.12.9",
+    "rxjs": "^5.5.6",
+    "zone.js": "^0.8.19"
+  },
+  "devDependencies": {
+    "@angular/cli": "~1.7.2",
+    "@angular/compiler-cli": "^5.2.0",
+    "@angular/language-service": "^5.2.0",
+    "codelyzer": "^4.2.1",
+    "rxjs-tslint-rules": "^3.14.0",
+    "tslint": "^5.9.1",
+    "typescript": "~2.5.3"
+  }
+}
diff --git a/proxy.conf.json b/proxy.conf.json
new file mode 100644
index 0000000..a277703
--- /dev/null
+++ b/proxy.conf.json
@@ -0,0 +1,10 @@
+{
+    "/api": {
+        "target": "http://localhost:7776";,
+        "secure": false,
+        "pathRewrite": {
+            "^/api": ""
+        },
+        "changeOrigin": true
+    }
+}
\ No newline at end of file
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
new file mode 100644
index 0000000..9b75a7e
--- /dev/null
+++ b/src/app/app-routing.module.ts
@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { IdentityListComponent } from 
'./identity-list/identity-list.component';
+
+const routes: Routes = [
+    { path: 'identities', component: IdentityListComponent },
+    { path: '', redirectTo: '/identities', pathMatch: 'full' },
+];
+
+@NgModule({
+    imports: [RouterModule.forRoot(routes, { useHash: true })],
+    exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/src/app/app.component.html b/src/app/app.component.html
new file mode 100644
index 0000000..183735d
--- /dev/null
+++ b/src/app/app.component.html
@@ -0,0 +1,2 @@
+<div class="logo"><img src="assets/reclaim_icon.png"/></div>
+<router-outlet></router-outlet>
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
new file mode 100644
index 0000000..7a249b0
--- /dev/null
+++ b/src/app/app.component.scss
@@ -0,0 +1,19 @@
+
+@font-face {
+  font-family: 'Audiowide';
+  src: url('assets/Audiowide-Regular.ttf');
+  font-weight: normal;
+  font-style: normal;
+}
+
+.logo {
+  font-family: Audiowide;
+  font-size: 32px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+  background-color: #fff;
+  text-align: center;
+}
+
+.logo img {
+    width: 200px;
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
new file mode 100644
index 0000000..7d943bc
--- /dev/null
+++ b/src/app/app.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+  title = 'app';
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
new file mode 100644
index 0000000..3e988e5
--- /dev/null
+++ b/src/app/app.module.ts
@@ -0,0 +1,44 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { APP_INITIALIZER, NgModule } from '@angular/core';
+import { HttpClientModule } from '@angular/common/http';
+import { FormsModule } from '@angular/forms';
+
+import { AppComponent } from './app.component';
+import { IdentityListComponent } from 
'./identity-list/identity-list.component';
+import { AppRoutingModule } from './app-routing.module';
+import { IdentityService } from './identity.service';
+import { ReclaimService } from './reclaim.service';
+import { NamestoreService } from './namestore.service';
+import { GnsService } from './gns.service';
+import { ConfigService } from './config.service';
+
+import './rxjs';
+import { OpenIdService } from './open-id.service';
+
+@NgModule({
+  declarations: [
+    AppComponent,
+    IdentityListComponent,
+  ],
+  imports: [
+    BrowserModule,
+    AppRoutingModule,
+    HttpClientModule,
+    FormsModule
+  ],
+  providers: [
+    IdentityService,
+    ReclaimService,
+    NamestoreService,
+    GnsService,
+    OpenIdService,
+    ConfigService,
+    {
+      provide: APP_INITIALIZER,
+      useFactory: (config: ConfigService) => () => config.load(),
+      deps: [ConfigService], multi: true
+    }
+  ],
+  bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/src/app/attribute.ts b/src/app/attribute.ts
new file mode 100644
index 0000000..8f17fb8
--- /dev/null
+++ b/src/app/attribute.ts
@@ -0,0 +1,5 @@
+export class Attribute {
+    constructor(public name: string,
+      public value: string,
+      public type: string) { }
+}
diff --git a/src/app/config.service.ts b/src/app/config.service.ts
new file mode 100644
index 0000000..c0eefd0
--- /dev/null
+++ b/src/app/config.service.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Config } from './config';
+
+@Injectable()
+export class ConfigService {
+  config: Config;
+
+  constructor(private http: HttpClient) { }
+
+  get() {
+    return this.config;
+  }
+
+  async load() {
+    this.config = await 
this.http.get<Config>('assets/config.json').toPromise();
+  }
+}
diff --git a/src/app/config.ts b/src/app/config.ts
new file mode 100644
index 0000000..7885b88
--- /dev/null
+++ b/src/app/config.ts
@@ -0,0 +1,3 @@
+export class Config {
+    constructor(public apiUrl: string) { }
+}
diff --git a/src/app/gns-response.ts b/src/app/gns-response.ts
new file mode 100644
index 0000000..ae2b2f7
--- /dev/null
+++ b/src/app/gns-response.ts
@@ -0,0 +1,3 @@
+export class GnuNetResponse {
+    constructor(public data: any) { }
+}
diff --git a/src/app/gns.service.ts b/src/app/gns.service.ts
new file mode 100644
index 0000000..1f0be69
--- /dev/null
+++ b/src/app/gns.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+
+import { ConfigService } from './config.service';
+
+@Injectable()
+export class GnsService {
+
+  constructor(private http: HttpClient, private config: ConfigService) { }
+
+  getClientName(client_id) {
+    return this.http.get<any[]>(this.config.get().apiUrl 
+      + '/gns/+.'+client_id+'?record_type=RECLAIM_OIDC_CLIENT');
+  }
+
+}
diff --git a/src/app/gnu-net-response.ts b/src/app/gnu-net-response.ts
new file mode 100644
index 0000000..ae2b2f7
--- /dev/null
+++ b/src/app/gnu-net-response.ts
@@ -0,0 +1,3 @@
+export class GnuNetResponse {
+    constructor(public data: any) { }
+}
diff --git a/src/app/identity-list/identity-list.component.html 
b/src/app/identity-list/identity-list.component.html
new file mode 100644
index 0000000..29d5a52
--- /dev/null
+++ b/src/app/identity-list/identity-list.component.html
@@ -0,0 +1,214 @@
+<div *ngIf="inOpenIdFlow()" class="alert alert-primary alert-dismissible fade 
show" role="alert">
+  <span class="fa fa-openid"></span>  <strong>"{{ clientName }}"</strong> asks 
you to share personal information.<br/>
+  Choose an identity to let it access the following information:
+  <ul>
+    <li *ngFor="let attribute of getScopes()"><strong>{{attribute}}</strong>
+  </ul>
+  <button class="btn btn-danger">
+    <span class="fa fa-ban"></span> Do not share information and return to 
website
+  </button>
+</div>
+<div *ngIf="0 == identities.length && !isAddIdentity()" style="text-align: 
center;" class="alert alert-secondary alert-dismissible fade show" role="alert">
+  You don't have any identities yet.<br/><br/>
+  <button class="btn btn-primary" (click)="addIdentity()">
+    <span class="fa fa-plus"></span> Add your first identity!
+  </button>
+</div>
+
+<div class="card" *ngIf="(null != identityInEdit) && !isAddIdentity()">
+  <div class="card-avatar card-img-top">
+    <div class="card-avatar-character text-dark">
+      Manage identity: {{ identityInEdit.name }}
+    </div>
+  </div>
+  <div class="card-body">
+    <div>
+
+      <h6 class="card-subtitle mb-2">Attributes:
+      </h6>
+
+
+      <table class="table pb-1" *ngIf="isAttributeMissing(identityInEdit)">
+        <tbody>
+          <tr [class.openid]="inOpenIdFlow()" 
[class.alert-danger]="newAttribute.name === missing.name" class="text-primary" 
*ngFor="let missing of missingAttributes[identityInEdit.pubkey]">
+            <td><div style="min-width: 15em">{{missing.name}}</div></td>
+            <td>
+              <input placeholder="Value" [(ngModel)]="missing.value">
+            </td>
+            <td>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+      <table class="table pb-1" style="">
+        <tbody>
+          <tr [class.openid]="inOpenIdFlow()" 
[class.text-primary]="isRequested(identityInEdit, attribute)" 
[class.alert-danger]="newAttribute.name === attribute.name" *ngFor="let 
attribute of attributes[identityInEdit.pubkey]">
+            <td><div style="min-width: 15em">{{attribute.name}}</div></td>
+            <td>
+              <input placeholder="Value" [(ngModel)]="attribute.value">
+            </td>
+            <td>
+              <button class="btn btn-primary" 
(click)="deleteAttribute(attribute)">
+                <span class="fa fa-trash"></span>
+              </button>
+            </td>
+          </tr>
+          <tr [class.alert-danger]="isInConflict(identityInEdit,newAttribute)">
+            <td>
+              <input 
[class.text-danger]="!attributeNameValid(identityInEdit,newAttribute)" 
placeholder="Attribute" [(ngModel)]="newAttribute.name">
+            </td>
+            <td>
+              <input placeholder="Value" 
[class.text-danger]="!attributeValueValid(newAttribute)" 
[(ngModel)]="newAttribute.value">
+            </td>
+            <td>
+              <button 
[disabled]="!canAddAttribute(identityInEdit,newAttribute)" class="btn 
btn-primary"  (click)="addAttribute(newAttribute)">
+                <span class="fa fa-plus"></span> 
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <div *ngIf="!attributeNameValid(identityInEdit,newAttribute) || 
!attributeValueValid(newAttribute)" class="alert alert-primary 
alert-dismissible fade show" role="alert">
+      <span class="fa fa-warning"></span> Note:
+      <ul>
+        <li>Only use alphanumeric attribute names.</li>
+        <li>You cannot define the same name twice.</li>
+        <li>Attribute values may not be empty!</li>
+      </ul>
+    </div>
+
+    <div style="margin-top: 1.5em;">
+      <h6 class="card-subtitle mb-2" *ngIf="identityInEdit == 
showTicketsIdentity">Authorized Entities:
+      </h6>
+
+      <table class="table pb-1" *ngIf="identityInEdit == showTicketsIdentity">
+        <tbody>
+          <tr *ngFor="let ticket of tickets[identityInEdit.pubkey]">
+            <td><div style="min-width: 
15em">{{identityNameMapper[ticket.audience]}}</div></td>
+            <td>
+              <button class="btn btn-primary" 
(click)="revokeTicket(identityInEdit, ticket)">
+                <span class="fa fa-unlink"></span> Revoke
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <div>
+      <button class="btn btn-primary" 
(click)="saveIdentityAttributes(identityInEdit)" 
[disabled]="!canSaveIdentity(identityInEdit)">
+        <span class="fa fa-save"></span> Save and Back
+      </button>
+      <button *ngIf="(0 < tickets[identityInEdit.pubkey]?.length) && 
!inOpenIdFlow()" class="btn btn-primary" 
(click)="toggleShowTickets(identityInEdit)" [style.float]="'right'">
+        <span class="fa fa-openid"></span><span *ngIf="identityInEdit == 
showTicketsIdentity"> Hide</span><span *ngIf="identityInEdit != 
showTicketsIdentity"> Show</span> Authorizations
+      </button>
+    </div>
+  </div>
+
+</div>
+<div class="card-columns p-2" *ngIf="(null == identityInEdit) && 
!isAddIdentity()">
+  <div class="card" *ngFor="let identity of identities">
+    <div class="card-avatar card-img-top">
+      <div class="card-avatar-character text-dark">
+        <div class="icon m-1 text-uppercase" 
[style.background-color]="intToRGB(identity.pubkey)">{{ identity.name[0]}}</div>
+        <span class="m-1" styl="display: inline-block">{{ 
identity.name}}</span>
+        <button class="btn btn-primary" *ngIf="showConfirmDelete != identity" 
(click)="confirmDelete(identity)">
+          <span class="fa fa-trash"></span>
+        </button>
+                <button class="btn btn-primary" *ngIf="showConfirmDelete != 
identity" (click)="editIdentity(identity)">
+          <span class="fa fa-edit"></span>
+        </button>
+      </div>
+    </div>
+    <div class="alert alert-danger fade show" *ngIf="showConfirmDelete == 
identity">
+      Do you really want to delete this identity?<br/><br/>
+      <button class="btn btn-primary" (click)="deleteIdentity(identity)">
+        <span class="fa fa-trash"></span> Delete
+      </button>
+      <button class="btn btn-primary" (click)="hideConfirmDelete()">
+        <span class="fa fa-close"></span> Cancel
+      </button>
+    </div>
+    <div class="alert alert-secondary fade show" 
*ngIf="!hasAttributes(identity)">
+      This identity has no attributes. Maybe try <a class="buttonlink" 
(click)="editIdentity(identity)">adding some?</a>
+    </div>
+    <div *ngIf="isAttributeMissing(identity)" class="alert alert-danger 
alert-dismissible fade show" role="alert">
+      <span class="fa fa-openid"></span> This identity cannot be used because 
it's missing requested attributes: 
+      <ul>
+        <li *ngFor="let attr of getMissing(identity)">{{attr}}</li>
+      </ul>
+      <button class="btn btn-primary" (click)="editIdentity(identity)">
+        <span class="fa fa-plus"></span> Add
+      </button>
+    </div>
+    <div class="card-body">
+      <div>
+        <h6 class="card-subtitle mb-2" *ngIf="hasAttributes(identity)">
+          <strong>Attributes:</strong>
+        </h6>
+        <h6 class="card-subtitle mb-2" *ngIf="!hasAttributes(identity)">
+          This identity has no attributes
+        </h6>
+        <table class="table pb-1">
+          <tbody>
+            <tr [class.openid]="inOpenIdFlow()" 
[class.text-primary]="isRequested(identity, attribute)"
+               [class.alert-danger]="newAttribute.name === attribute.name" 
*ngFor="let attribute of attributes[identity.pubkey]">
+              <td>
+                <div style="min-width: 15em">{{attribute.name}}</div>
+              </td>
+              <td>
+                <div style="min-width: 15em">{{attribute.value}}</div>
+              </td>
+              <td>
+                <div style="min-width: 8em;">
+                  <span *ngIf="isRequested(identity, attribute)" 
[style.float]="'right'" class="fa fa-openid" ></span>
+                </div>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+      <div style="margin-bottom: 1em" *ngIf="(0 < 
attributes[identity.pubkey]?.length) && inOpenIdFlow()" class="text-primary">
+        Only attributes marked with <span class="fa fa-openid" ></span> will 
be shared.
+      </div>
+
+      <div>
+        <button *ngIf="canAuthorize(identity)" [disabled]="!inOpenIdFlow() || 
isAttributeMissing(identity)" (click)="loginIdentity(identity)" class="btn 
btn-primary mr-1 openid-login">
+          <span class="fa fa-openid"></span> Share from this identity</button>
+      </div>
+    </div>
+  </div>
+
+
+</div>
+<div *ngIf="isAddIdentity()" class="card" style="text-align:center;">
+  <div class="card-avatar card-img-top">
+    <div class="card-avatar-character text-dark">
+      New Identity        </div>
+  </div>
+  <div *ngIf="'' !== newIdentity.name && !canSave()" class="alert alert-danger 
alert-dismissible fade show" role="alert">
+    Only Alphanumeric input. No spaces or special characters allowed.
+  </div>
+  <div *ngIf="'' === newIdentity.name || canSave()" class="alert 
alert-secondary alert-dismissible fade show" role="alert">
+    Enter a username for your new identity
+  </div>
+  <div class="card-body">
+    <input [class.text-danger]="'' !== newIdentity.name && !canSave()" 
style="text-align: center; border: 1px solid #111;" 
(keyup.enter)="saveIdentity()" pattern="^[a-zA-Z0-9-]+" placeholder="Username" 
title="Only Alphanumeric input. No spaces or special characters allowed." 
class="mr-2" [(ngModel)]="newIdentity.name" autofocus>
+    <br/>
+    <br/>
+    <button [disabled]="!canSave()" [style.inactive]="!canSave()"  class="btn 
btn-primary" (click)="saveIdentity()">
+      <span class="fa fa-save"></span> Save
+    </button>
+    <button class="btn btn-danger" (click)="cancelAddIdentity()">
+      <span class="fa fa-close"></span> Cancel
+    </button>
+  </div>
+</div>
+<div *ngIf="0 != identities.length && !isAddIdentity() && (null == 
identityInEdit)"  style="margin-top: 1em; text-align: center;">
+  <button class="btn btn-primary" (click)="addIdentity()">
+    <span class="fa fa-plus"></span> Add identity
+  </button>
+</div>
+
+
diff --git a/src/app/identity-list/identity-list.component.scss 
b/src/app/identity-list/identity-list.component.scss
new file mode 100644
index 0000000..5cba5ef
--- /dev/null
+++ b/src/app/identity-list/identity-list.component.scss
@@ -0,0 +1,46 @@
+.card table {
+  table-layout: fixed;
+}
+
+.card table input {
+  width: 100%;
+}
+
+div.card-avatar {
+    text-align: center;
+    border-bottom: 2px solid rgba(0, 0, 0, 0.125);
+    //min-height: 50px;
+}
+
+div.card-avatar-character {
+    font-size: 2em;
+    border: 1px solid white;
+}
+
+div.card-avatar-id {
+    font-size: 1em;
+}
+
+.card.selected {
+    box-shadow: 0px 1px 2px -1px rgba(0, 0, 0, 0.5);
+    //border-color: #343a40;
+}
+
+.card tr.openid.text-dimmed {
+   color: #eee;
+}
+
+.card-avatar .btn-primary {
+  border: none;
+  background-color: rgba(0,0,0,0);
+  color: #555;
+  font-size: 0.75em;
+  float: right;
+}
+
+.card-avatar div.icon {
+  border-radius: 5em;
+  width: 1.5em;
+  color: white;
+  display: inline-block;
+}
diff --git a/src/app/identity-list/identity-list.component.ts 
b/src/app/identity-list/identity-list.component.ts
new file mode 100644
index 0000000..96ac308
--- /dev/null
+++ b/src/app/identity-list/identity-list.component.ts
@@ -0,0 +1,435 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { IdentityService } from '../identity.service';
+import { OpenIdService } from '../open-id.service';
+import { Identity } from '../identity';
+import { Attribute } from '../attribute';
+import { Ticket } from '../ticket';
+import { ReclaimService } from '../reclaim.service';
+import { NamestoreService } from '../namestore.service';
+import { GnsService } from '../gns.service';
+import { Observable } from 'rxjs/Rx'
+import 'rxjs/add/observable/forkJoin';
+
+@Component({
+  selector: 'app-identity-list',
+  templateUrl: './identity-list.component.html',
+  styleUrls: ['./identity-list.component.scss']
+})
+export class IdentityListComponent implements OnInit {
+
+  requestedAttributes: any;
+  missingAttributes: any;
+  attributes: any;
+  tickets: any;
+  clientName: any;
+  identities: Identity[];
+  newIdentity: Identity;
+  newAttribute: Attribute;
+  identityInEdit: Identity;
+  identityInEditName: string;
+  identityNameMapper: any;
+  showTicketsIdentity: Identity;
+  showConfirmDelete: any;
+
+  constructor(private route: ActivatedRoute,
+    private router: Router,
+    private oidcService: OpenIdService,
+    private identityService: IdentityService,
+    private reclaimService: ReclaimService,
+    private namestoreService: NamestoreService,
+    private gnsService: GnsService) { }
+
+  ngOnInit() {
+    this.attributes = {};
+    this.tickets = {};
+    this.identities = [];
+    this.showConfirmDelete = null;
+    this.newAttribute = new Attribute('', '', 'STRING');
+    this.requestedAttributes = {};
+    this.missingAttributes = {};
+    this.oidcService.parseRouteParams(this.route.snapshot.queryParams);
+    this.getClientName();
+    //this.newIdentity = new Identity('', '', {});
+    this.identityInEditName = "";
+    this.identityNameMapper = {};
+    this.updateIdentities();
+  }
+  
+  confirmDelete(identity) {
+    this.showConfirmDelete = identity;
+  }
+
+  hideConfirmDelete() {
+    this.showConfirmDelete = null;
+  }
+
+  getClientName() {
+    if (!this.inOpenIdFlow()) {
+      this.clientName = "-";
+      return;
+    }
+    this.clientName = this.oidcService.getClientId();
+    this.gnsService.getClientName(this.oidcService.getClientId()).subscribe 
(records => {
+      console.log (records);
+      for (var i = 0; i < records.length; i++) {
+        if (records[i].record_type !== "RECLAIM_OIDC_CLIENT")
+          continue;
+        this.clientName = records[i].value;
+        break;
+      }
+    });
+  }
+
+  intToRGB(i) {
+    i = this.hashCode(i);
+    const c = (i & 0x00FFFFFF)
+      .toString(16)
+      .toUpperCase();
+
+    return '#' + '00000'.substring(0, 6 - c.length) + c;
+  }
+
+  hashCode(str) {
+    let hash = 0;
+    for (let i = 0; i < str.length; i++) {
+      hash = str.charCodeAt(i) + ((hash << 5) - hash);
+    }
+    return hash;
+  }
+
+  isAddIdentity() {
+    return null != this.newIdentity;
+  }
+
+  canSave() {
+    if (this.newIdentity.name == null) {
+      return false;
+    }
+    if (this.newIdentity.name === '') {
+      return false;
+    }
+    if (!/^[a-zA-Z0-9-]+$/.test(this.newIdentity.name)) {
+      return false;
+    }
+    return true;
+  }
+
+  addIdentity() {
+    this.newIdentity = new Identity ('','');
+  }
+
+  editIdentity(identity) {
+    this.identityInEdit = identity;
+    this.showTicketsIdentity = null;
+  }
+
+  isInEdit(identity) {
+    return this.identityInEdit == identity;
+  }
+
+  saveIdentityAttributes(identity) {
+    this.storeAttributes(identity)
+    .finally(() => this.updateAttributes(identity))  
+    .subscribe(
+      res => console.log(res), 
+      error => {return Observable.empty()},
+      () => { 
+        this.identityInEdit = null;
+        this.updateAttributes(identity);
+      }
+    );
+    this.newAttribute.name = '';
+    this.newAttribute.value = '';
+    this.newAttribute.type = "STRING";
+    this.identityInEdit = null;
+  }
+
+  deleteAttribute(attribute) {
+    this.namestoreService.deleteName(this.identityInEdit, 
attribute.name).subscribe (data => {
+      this.updateAttributes(this.identityInEdit);
+    });
+  }
+  getMissingAttributes(identity) {
+    let scopes = this.getScopes();
+    var i;
+    for (i = 0; i < this.requestedAttributes[identity.pubkey].length; i++) {
+      const j = 
scopes.indexOf(this.requestedAttributes[identity.pubkey][i].name);
+      if (j >= 0) {
+        scopes.splice(j, 1);
+      }
+    }
+    this.missingAttributes[identity.pubkey] = [];
+    for (i = 0; i < scopes.length; i++) {
+      let attribute = new Attribute('', '', 'STRING');
+      attribute.name = scopes[i];
+      this.missingAttributes[identity.pubkey].push(attribute);
+    }
+  }
+
+  private updateTickets(identity) {
+    this.reclaimService.getTickets(identity).subscribe(tickets => {
+      this.tickets[identity.pubkey] = [];
+      if (tickets === null) {
+        return;
+      }
+      this.tickets[identity.pubkey] = tickets;
+      tickets.forEach((ticket) => {
+        this.gnsService.getClientName(ticket.audience).subscribe(records => {
+          for (var i = 0; i < records.length; i++) {
+            if (records[i].type !== "RECLAIM_OIDC_CLIENT")
+              continue;
+            this.identityNameMapper[ticket.audience] = records[i].value;
+            break;
+          }
+        });
+      });
+    });
+  }
+
+  toggleShowTickets(identity) {
+    if (this.showTicketsIdentity == identity) {
+      this.showTicketsIdentity = null;
+      return;
+    }
+    this.showTicketsIdentity = identity;
+  }
+
+  revokeTicket(identity, ticket) {
+    this.reclaimService.revokeTicket(ticket).subscribe(data => {
+      this.updateTickets(identity);
+    });
+  }
+
+
+  private updateAttributes(identity) {
+    this.reclaimService.getAttributes(identity).subscribe(attributes => {
+      this.attributes[identity.pubkey] = [];
+      this.requestedAttributes[identity.pubkey] = [];
+      if (attributes === null) {
+        this.getMissingAttributes(identity);
+        return;
+      }
+      var i;
+      for (i = 0; i < attributes.length; i++) {
+        this.attributes[identity.pubkey].push(attributes[i]);
+        if (this.oidcService.getScope().includes(attributes[i].name)) {
+          this.requestedAttributes[identity.pubkey].push(attributes[i]);
+        }
+
+      }
+      this.getMissingAttributes(identity);
+    });
+
+  }
+
+  saveAttribute(identity, attribute) {
+    return this.reclaimService.addAttribute(identity, attribute).subscribe 
(data => {
+      //this.updateAttributes(identity);
+    });
+  }
+
+  private storeAttributes(identity) {
+    var promises = [];
+    var i;
+    for (i = 0; i < this.missingAttributes[identity.pubkey].length; i++) {
+      if (this.missingAttributes[identity.pubkey][i].value === "") {
+        continue;
+      }
+      promises.push(this.saveAttribute(identity, 
this.missingAttributes[identity.pubkey][i]));
+    }
+    for (i = 0; i < this.attributes[identity.pubkey].length; i++) {
+      promises.push(this.saveAttribute(identity, 
this.attributes[identity.pubkey][i]));
+    }
+    if (this.newAttribute.value !== "") {
+      promises.push(this.saveAttribute(identity, this.newAttribute));
+    }
+    return Observable.forkJoin(promises)
+  }
+
+  addAttribute(attribute) {
+    this.storeAttributes(this.identityInEdit)
+      .finally(() => this.updateAttributes(this.identityInEdit))  
+      .subscribe(
+        res => console.log(res), 
+        error => {return Observable.empty()},
+        () => { 
+          this.newAttribute.name = '';
+          this.newAttribute.value = '';
+          this.newAttribute.type = "STRING";
+          this.updateAttributes(this.identityInEdit);
+        }
+      );
+    this.newAttribute.name = '';
+    this.newAttribute.value = '';
+  }
+
+  cancelAddIdentity() {
+    this.newIdentity = null;
+  }
+
+  saveIdentity() {
+    if (!this.canSave()) {
+      return;
+    }
+    this.identityInEditName = this.newIdentity.name;
+    this.identityService.addIdentity(this.newIdentity).subscribe(data => {
+      this.newIdentity.name = '';
+      this.updateIdentities();
+      this.cancelAddIdentity();
+    });
+  }
+
+  deleteIdentity(identity) {
+    this.showConfirmDelete = false;
+    this.identityInEdit = null;
+    this.identityService.deleteIdentity(identity.pubkey).subscribe(data => {
+      this.updateIdentities();
+    });
+  }
+
+
+  loginIdentity(identity) {
+    this.oidcService.login(identity).subscribe(data => {
+      console.log('Successfully logged in');
+      this.authorize();
+    });
+  }
+
+  authorize() {
+    this.oidcService.authorize();
+  }
+
+  inOpenIdFlow() {
+    return this.oidcService.inOpenIdFlow();
+  }
+  
+  canAddAttribute(identity,attribute) {
+    if ((attribute.name === "") || (attribute.value == "")) {
+      return false;
+    }
+    if (attribute.name.indexOf(" ") >= 0) {
+      return false;
+    }
+    return !this.isInConflict(identity,attribute);
+  }
+
+  attributeNameValid(identity, attribute) {
+    if (attribute.name === "" && attribute.value === "") {
+      return true;
+    }
+    if (attribute.name.indexOf(" ") >= 0) {
+      return false;
+    }
+    if (!/^[a-zA-Z0-9-]+$/.test(attribute.name)) {
+      return false;
+    }
+    return !this.isInConflict(identity,attribute);
+  }
+
+  attributeValueValid(attribute) {
+    if (attribute.value === "") {
+      return attribute.name === "";
+    }
+    return true;
+  }
+
+  canSaveIdentity(identity) {
+    if (this.canAddAttribute(identity,this.newAttribute)) {
+      return true;
+    }
+    return ((this.newAttribute.name === "") && (this.newAttribute.value === 
"")) && !this.isInConflict(identity,this.newAttribute);
+  }
+
+  isInConflict(identity,attribute) {
+    var i;
+    if (undefined !== this.missingAttributes[identity.pubkey]) {
+      for (i = 0; i < this.missingAttributes[identity.pubkey].length; i++) {
+        if (attribute.name === 
this.missingAttributes[identity.pubkey][i].name) {
+          return true;
+        }
+      }
+    }
+    if (undefined !== this.attributes[identity.pubkey]) {
+      for (i = 0; i < this.attributes[identity.pubkey].length; i++) {
+        if (attribute.name === this.attributes[identity.pubkey][i].name) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  getScopes() {
+    return this.oidcService.getScope();
+  }
+
+  getScopesPretty() {
+    return this.getScopes().join(", ");
+  }
+  getMissing(identity) {
+    var arr = [];
+    var i = 0;
+    for (i = 0; i < this.missingAttributes[identity.pubkey].length; i++) {
+      arr.push(this.missingAttributes[identity.pubkey][i].name);
+    }
+    return arr;
+  }
+  getMissingPretty(identity) {
+    return this.getMissing(identity).join(", ");
+  }
+  canAuthorize(identity) {
+    return this.inOpenIdFlow() && !this.isInEdit(identity);
+  }
+  isRequested(identity, attribute) {
+    if (undefined === this.requestedAttributes[identity.pubkey]) {
+      return false;
+    } else {
+      return -1 !== 
this.requestedAttributes[identity.pubkey].indexOf(attribute);
+    }
+  }
+
+  isAttributeMissing(identity) {
+    if (!this.inOpenIdFlow()) {
+      return false;
+    }
+    if (undefined === this.requestedAttributes[identity.pubkey]) {
+      return false;
+    }
+    return this.getScopes().length !== 
this.requestedAttributes[identity.pubkey].length;
+  }
+
+  hasAttributes(identity) {
+    if (undefined === this.attributes[identity.pubkey]) {
+      return false;
+    }
+    return 0 !== this.attributes[identity.pubkey].length
+  }
+
+  private updateIdentities() {
+    this.identityService.getIdentities().subscribe(identities => {
+      this.identities = [];
+      var i;
+      this.identityNameMapper = {};
+      for (i = 0; i < identities.length; i++) {
+        //"reclaim" is the reclaim UI and API namespace!
+        this.identityNameMapper[identities[i].pubkey] = identities[i].name;
+        if ("reclaim" === identities[i].name) {
+          continue;
+        }
+        this.identities.push(identities[i]);
+        if (this.identityInEditName === identities[i].name) {
+          this.editIdentity(this.identities[this.identities.length-1]);
+          this.identityInEditName = "";
+        }
+      }
+
+      identities.forEach(identity => {
+        if ("id" !== identity.name && "io" !== identity.name) {
+          this.updateAttributes(identity);
+          this.updateTickets(identity);
+        }
+      });
+    });
+  }
+}
diff --git a/src/app/identity.service.ts b/src/app/identity.service.ts
new file mode 100644
index 0000000..f03f716
--- /dev/null
+++ b/src/app/identity.service.ts
@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+
+import { ConfigService } from './config.service';
+import { Identity } from './identity';
+import { GnuNetResponse } from './gnu-net-response';
+
+@Injectable()
+export class IdentityService {
+  constructor(private http: HttpClient, private config: ConfigService) { }
+
+  getIdentities(): Observable<Identity[]> {
+    return this.http.get<any[]>(this.config.get().apiUrl + '/identity/all');
+  }
+
+  getIdentity(identityId: string) {
+    return this.http.get(this.config.get().apiUrl + '/identity/pubkey/' + 
identityId);
+  }
+
+  addIdentity(identity: Identity) {
+    var obj = {'name': identity.name};
+    return this.http.post(this.config.get().apiUrl + '/identity/', obj);
+  }
+
+  deleteIdentity(identityId: string) {
+    return this.http.delete(this.config.get().apiUrl + '/identity/pubkey/' + 
identityId);
+  }
+}
diff --git a/src/app/identity.ts b/src/app/identity.ts
new file mode 100644
index 0000000..1a0a6f8
--- /dev/null
+++ b/src/app/identity.ts
@@ -0,0 +1,4 @@
+export class Identity {
+    constructor(public pubkey: string,
+        public name: string) { }
+}
diff --git a/src/app/namestore-response.ts b/src/app/namestore-response.ts
new file mode 100644
index 0000000..ae2b2f7
--- /dev/null
+++ b/src/app/namestore-response.ts
@@ -0,0 +1,3 @@
+export class GnuNetResponse {
+    constructor(public data: any) { }
+}
diff --git a/src/app/namestore.service.ts b/src/app/namestore.service.ts
new file mode 100644
index 0000000..21a4939
--- /dev/null
+++ b/src/app/namestore.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+
+import { Identity } from './identity';
+import { ConfigService } from './config.service';
+
+@Injectable()
+export class NamestoreService {
+
+  constructor(private http: HttpClient, private config: ConfigService) { }
+
+  deleteName(identity: Identity, name: string) {
+    return this.http.delete(this.config.get().apiUrl + '/namestore/?label=' + 
name + "&name=" + identity.name);
+  }
+  
+}
diff --git a/src/app/open-id.service.ts b/src/app/open-id.service.ts
new file mode 100644
index 0000000..fce807d
--- /dev/null
+++ b/src/app/open-id.service.ts
@@ -0,0 +1,52 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Identity } from './identity';
+import { ConfigService } from './config.service';
+import { Router } from '@angular/router';
+
+@Injectable()
+export class OpenIdService {  
+  params: any;
+
+  constructor(private http: HttpClient,
+    private config: ConfigService,
+    private router: Router) { }
+
+  login(identity: Identity) {
+    return this.http.post(this.config.get().apiUrl + '/openid/login', { 
'identity': identity.pubkey});
+  }
+
+  parseRouteParams(params: any): any {
+    if (undefined !== this.params) {
+      return;
+    }
+    this.params = params;
+  }
+
+  authorize(): any {
+    window.location.href = this.config.get().apiUrl + 
'/openid/authorize?client_id=' + this.params['client_id'] +
+    '&redirect_uri=' + this.params['redirect_uri'] +
+    '&response_type=' + this.params['response_type'] +
+    '&scope=' + this.params['scope'] +
+    '&state=' + this.params['state'] +
+    '&nonce=' + this.params['nonce'];
+  }
+
+  inOpenIdFlow(): any {
+    return this.params['redirect_uri'] !== undefined;
+  }
+
+  getClientId(): any {
+    return this.params['client_id'];
+  }
+
+  getScope(): any {
+    if (!this.inOpenIdFlow()) {
+      return [];
+    }
+    var scopes = this.params['scope'].split(" ");
+    const i = scopes.indexOf("openid");
+    scopes.splice(i, 1);
+    return scopes;
+  }
+}
diff --git a/src/app/reclaim.service.ts b/src/app/reclaim.service.ts
new file mode 100644
index 0000000..31b9ccb
--- /dev/null
+++ b/src/app/reclaim.service.ts
@@ -0,0 +1,34 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+
+import { ConfigService } from './config.service';
+import { Identity } from './identity';
+import { Attribute } from './attribute';
+import { Ticket } from './ticket';
+import { GnuNetResponse } from './gnu-net-response';
+
+@Injectable()
+export class ReclaimService {
+
+  constructor(private http: HttpClient, private config: ConfigService) { }
+
+  getAttributes(identity: Identity): Observable<Attribute[]> {
+    return this.http.get<Attribute[]>(this.config.get().apiUrl + 
'/reclaim/attributes/'
+     + identity.name);
+  }
+
+  addAttribute(identity: Identity, attribute: Attribute) {
+    return this.http.post<Attribute>(this.config.get().apiUrl + 
'/reclaim/attributes/' + identity.name, attribute);
+  }
+
+  getTickets(identity: Identity): Observable<Ticket[]> {
+    return this.http.get<Ticket[]>(this.config.get().apiUrl + 
'/reclaim/tickets/'
+     + identity.name);
+  }
+  
+  revokeTicket(ticket: Ticket) {
+    return this.http.post<Ticket>(this.config.get().apiUrl + 
'/reclaim/revoke', ticket);
+  }
+
+}
diff --git a/src/app/record.ts b/src/app/record.ts
new file mode 100644
index 0000000..e7a5199
--- /dev/null
+++ b/src/app/record.ts
@@ -0,0 +1,5 @@
+export class Record {
+  constructor(public value: string,
+      public expiration_time: string,
+      public label) { }
+}
diff --git a/src/app/rxjs.ts b/src/app/rxjs.ts
new file mode 100644
index 0000000..ea16726
--- /dev/null
+++ b/src/app/rxjs.ts
@@ -0,0 +1 @@
+import 'rxjs/add/operator/map';
diff --git a/src/app/ticket.ts b/src/app/ticket.ts
new file mode 100644
index 0000000..03527b9
--- /dev/null
+++ b/src/app/ticket.ts
@@ -0,0 +1,5 @@
+export class Ticket {
+    constructor(public identity: string,
+        public audience: string,
+        public rnd: string) { }
+}
diff --git a/src/assets/Audiowide-Regular.ttf b/src/assets/Audiowide-Regular.ttf
new file mode 100755
index 0000000..8b50bed
Binary files /dev/null and b/src/assets/Audiowide-Regular.ttf differ
diff --git a/src/assets/config.json b/src/assets/config.json
new file mode 100644
index 0000000..d8dc2d0
--- /dev/null
+++ b/src/assets/config.json
@@ -0,0 +1,3 @@
+{
+    "apiUrl": "https://api.reclaim";
+}
diff --git a/src/assets/reclaim_icon.png b/src/assets/reclaim_icon.png
new file mode 100644
index 0000000..4e23a1d
Binary files /dev/null and b/src/assets/reclaim_icon.png differ
diff --git a/src/assets/reclaim_small.png b/src/assets/reclaim_small.png
new file mode 100644
index 0000000..ffc0f54
Binary files /dev/null and b/src/assets/reclaim_small.png differ
diff --git a/src/environments/environment.prod.ts 
b/src/environments/environment.prod.ts
new file mode 100644
index 0000000..3612073
--- /dev/null
+++ b/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+  production: true
+};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
new file mode 100644
index 0000000..b7f639a
--- /dev/null
+++ b/src/environments/environment.ts
@@ -0,0 +1,8 @@
+// The file contents for the current environment will overwrite these during 
build.
+// The build system defaults to the dev environment which uses 
`environment.ts`, but if you do
+// `ng build --env=prod` then `environment.prod.ts` will be used instead.
+// The list of which env maps to which file can be found in 
`.angular-cli.json`.
+
+export const environment = {
+  production: false
+};
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..c4fc08f
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="utf-8">
+  <title>re:claimID</title>
+  <base href="/">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+</head>
+
+<body>
+  <app-root></app-root>
+</body>
+
+</html>
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..91ec6da
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,12 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+  .catch(err => console.log(err));
diff --git a/src/polyfills.ts b/src/polyfills.ts
new file mode 100644
index 0000000..af84770
--- /dev/null
+++ b/src/polyfills.ts
@@ -0,0 +1,79 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are 
sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded 
before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions 
of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 
(including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular 
decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Required to support Web Animations `@angular/platform-browser/animations`.
+ * Needed for: All but Chrome, Firefox and Opera. 
http://caniuse.com/#feat=web-animation
+ **/
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following 
flags
+ */
+
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable 
patch requestAnimationFrame
+ // (window as any).__Zone_disable_on_property = true; // disable patch 
onProperty such as onclick
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 
'mousemove']; // disable patch specified eventNames
+
+ /*
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by 
zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ */
+// (window as any).__Zone_enable_cross_context_check = true;
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/src/styles.scss b/src/styles.scss
new file mode 100644
index 0000000..8d871af
--- /dev/null
+++ b/src/styles.scss
@@ -0,0 +1,57 @@
+@import "node_modules/bootstrap/scss/bootstrap";
+$fa-font-path: "../node_modules/font-awesome/fonts";
+@import "node_modules/font-awesome/scss/font-awesome.scss";
+
+html {
+  /*font-family: Roboto, Helvetica, Arial, sans-serif;*/
+  margin: 0 auto;
+  font-size: 14px;
+}
+
+body {
+  background-color: #fafafa;
+}
+
+
+div.card {
+  //box-shadow: 0px 1px 2px -1px rgba(0, 0, 0, 0.5);
+  border-radius: 0;
+}
+
+.btn:disabled {
+  background-color: lightgray;
+}
+
+.btn {
+  border-radius: 0.25rem;
+}
+
+.btn-primary {
+  background-color: #fff;
+  color: #555;
+  border: 1px solid #ddd;
+}
+
+.btn-primary:hover {
+  background-color: #777;
+  border: 1px solid #777;
+}
+
+.btn-primary:disabled {
+  border: 1px solid #ccc;
+}
+
+.btn-primary:disabled:hover {
+  background-color: lightgrey;
+}
+
+.alert a.buttonlink {
+  text-decoration: underline;
+  cursor: pointer;
+  color: #777;
+}
+
+.alert {
+  border-radius: 0;
+  margin-bottom: 0;
+}
diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json
new file mode 100644
index 0000000..39ba8db
--- /dev/null
+++ b/src/tsconfig.app.json
@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "baseUrl": "./",
+    "module": "es2015",
+    "types": []
+  },
+  "exclude": [
+    "test.ts",
+    "**/*.spec.ts"
+  ]
+}
diff --git a/src/typings.d.ts b/src/typings.d.ts
new file mode 100644
index 0000000..ef5c7bd
--- /dev/null
+++ b/src/typings.d.ts
@@ -0,0 +1,5 @@
+/* SystemJS module definition */
+declare var module: NodeModule;
+interface NodeModule {
+  id: string;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..a6c016b
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "target": "es5",
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2017",
+      "dom"
+    ]
+  }
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..ecf0d49
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,154 @@
+{
+    "rulesDirectory": [
+        "node_modules/codelyzer",
+        "node_modules/rxjs-tslint-rules/dist/rules"
+    ],
+    "rules": {
+        "rxjs-add": {
+            "options": [
+                {
+                    "allowElsewhere": false,
+                    "allowUnused": false,
+                    "file": "./src/app/rxjs.ts"
+                }
+            ],
+            "severity": "error"
+        },
+        "arrow-return-shorthand": true,
+        "callable-types": true,
+        "class-name": true,
+        "comment-format": [
+            true,
+            "check-space"
+        ],
+        "curly": true,
+        "deprecation": {
+            "severity": "warn"
+        },
+        "eofline": true,
+        "forin": true,
+        "import-blacklist": [
+            true,
+            "rxjs",
+            "rxjs/Rx"
+        ],
+        "import-spacing": true,
+        "indent": [
+            true,
+            "spaces"
+        ],
+        "interface-over-type-literal": true,
+        "label-position": true,
+        "max-line-length": [
+            true,
+            140
+        ],
+        "member-access": false,
+        "member-ordering": [
+            true,
+            {
+                "order": [
+                    "static-field",
+                    "instance-field",
+                    "static-method",
+                    "instance-method"
+                ]
+            }
+        ],
+        "no-arg": true,
+        "no-bitwise": false,
+        "no-console": [
+            true,
+            "debug",
+            "info",
+            "time",
+            "timeEnd",
+            "trace"
+        ],
+        "no-construct": true,
+        "no-debugger": true,
+        "no-duplicate-super": true,
+        "no-empty": false,
+        "no-empty-interface": true,
+        "no-eval": true,
+        "no-inferrable-types": [
+            true,
+            "ignore-params"
+        ],
+        "no-misused-new": true,
+        "no-non-null-assertion": true,
+        "no-shadowed-variable": true,
+        "no-string-literal": false,
+        "no-string-throw": true,
+        "no-switch-case-fall-through": true,
+        "no-trailing-whitespace": true,
+        "no-unnecessary-initializer": true,
+        "no-unused-expression": true,
+        "no-use-before-declare": true,
+        "no-var-keyword": true,
+        "object-literal-sort-keys": false,
+        "one-line": [
+            true,
+            "check-open-brace",
+            "check-catch",
+            "check-else",
+            "check-whitespace"
+        ],
+        "prefer-const": true,
+        "quotemark": [
+            true,
+            "single"
+        ],
+        "radix": true,
+        "semicolon": [
+            true,
+            "always"
+        ],
+        "triple-equals": [
+            true,
+            "allow-null-check"
+        ],
+        "typedef-whitespace": [
+            true,
+            {
+                "call-signature": "nospace",
+                "index-signature": "nospace",
+                "parameter": "nospace",
+                "property-declaration": "nospace",
+                "variable-declaration": "nospace"
+            }
+        ],
+        "unified-signatures": true,
+        "variable-name": false,
+        "whitespace": [
+            true,
+            "check-branch",
+            "check-decl",
+            "check-operator",
+            "check-separator",
+            "check-type"
+        ],
+        "directive-selector": [
+            true,
+            "attribute",
+            "app",
+            "camelCase"
+        ],
+        "component-selector": [
+            true,
+            "element",
+            "app",
+            "kebab-case"
+        ],
+        "no-output-on-prefix": true,
+        "use-input-property-decorator": true,
+        "use-output-property-decorator": true,
+        "use-host-property-decorator": true,
+        "no-input-rename": true,
+        "no-output-rename": true,
+        "use-life-cycle-interface": true,
+        "use-pipe-transform-interface": true,
+        "component-class-suffix": true,
+        "directive-class-suffix": true
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]