add git-src mbs-ui

This commit is contained in:
hunan
2024-11-01 10:33:39 +08:00
commit c24d38c6bb
49 changed files with 1941 additions and 0 deletions

13
mbs-ui/.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

44
mbs-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
*.swp
# IDE - VSCode
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db

19
mbs-ui/.travis.yml Normal file
View File

@@ -0,0 +1,19 @@
sudo: required
dist: trusty
addons:
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
language: node_js
node_js:
- 8
before_script:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
install:
- npm install
script:
- npm run ci

36
mbs-ui/README.md Normal file
View File

@@ -0,0 +1,36 @@
# Module Build Service User Interface (MBS-UI)
A simple frontend to the Module Build Service written in Angular.
## Development
To setup a development environment, make sure you have `npm` installed.
Once npm is installed:
* Run `npm install` to install the dev and production dependencies
* Run `ng serve` to start the development server
* In your browser, you can now navigate to `http://localhost:4200/`
## Build
Although it's recommended to use OpenShift for builds and deployments, you can
manaully build MBS-UI for production with
`ng build --environment <environment_name>`. The build artifacts will be stored
in the `dist/` directory.
## Deployment
The recommended deployment method is OpenShift. The easiest way is to use the
OpenShift template provided in `openshift/mbs-ui-deployment-template.yaml` by
running `oc new-app mbs-ui-deployment-template.yaml -p NG_ENVIRONMENT=<environment_name>`.
You can override the route's hostname/FQDN with the `ROUTE_HOSTNAME` parameter.
Alternatively, you can do the more manual approach with the following steps:
* Add the S2I builder image for Angular apps with:
`oc create -f https://raw.githubusercontent.com/mprahl/s2i-angular-httpd24/master/s2i-angular-httpd24.yml`
* Build and deploy the application with:
`oc new-app s2i-angular-httpd24~https://github.com/release-engineering/mbs-ui --build-env NG_ENVIRONMENT=<environment_name>`
* Create a route with:
`oc expose svc/mbs-ui --port 8080`
* Perform any additional configuration such as adding readiness and health
probes

158
mbs-ui/angular.json Normal file
View File

@@ -0,0 +1,158 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"mbs": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
"src/favicon.ico"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"redhat-prod": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.redhat.ts"
}
]
},
"fedora-prod": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.fedora.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "mbs:build"
},
"configurations": {
"redhat-prod": {
"browserTarget": "mbs:build:redhat-prod"
},
"fedora-prod": {
"browserTarget": "mbs:build:fedora-prod"
},
"production": {
"browserTarget": "mbs:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "mbs:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.scss"
],
"assets": [
"src/assets",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "mbs",
"schematics": {
"@schematics/angular:class": {
"spec": false
},
"@schematics/angular:component": {
"spec": false,
"prefix": "app",
"styleext": "scss"
},
"@schematics/angular:directive": {
"spec": false,
"prefix": "app"
},
"@schematics/angular:guard": {
"spec": false
},
"@schematics/angular:module": {
"spec": false
},
"@schematics/angular:pipe": {
"spec": false
},
"@schematics/angular:service": {
"spec": false
}
}
}

31
mbs-ui/karma.conf.js Normal file
View File

@@ -0,0 +1,31 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@@ -0,0 +1,177 @@
apiVersion: v1
kind: Template
metadata:
annotations:
openshift.io/display-name: MBS UI
description: A frontend to the Module Build Service (MBS)
iconClass: icon-js
tags: javscript
name: mbs-ui-template
parameters:
- name: NG_ENVIRONMENT
displayName: Angular Environment
description: Name of the Angular environment to build from
required: true
- name: ROUTE_HOSTNAME
displayName: Route hostname/FQDN
description: Hostname/FQDN for the route to listen on
required: false
objects:
# The image stream for the S2I builder image
- apiVersion: v1
kind: ImageStream
metadata:
name: s2i-angular-httpd24
annotations:
openshift.io/display-name: Angular
spec:
tags:
- name: angular5
annotations:
openshift.io/display-name: Angular
description: >-
Build and run Angular 2-5 apps based on the official
centos/httpd-24-centos7 image. For more information about using this
builder image, see https://github.com/mprahl/s2i-angular-httpd24.
iconClass: icon-js
tags: 'builder,angular,javascript'
supports: 'angular'
from:
kind: DockerImage
name: 'mprahl/s2i-angular-httpd24:angular5'
# The image stream that will represent the built image. Without this, the build
# will fail.
- apiVersion: v1
kind: ImageStream
metadata:
labels:
app: mbs-ui
name: mbs-ui
spec:
tags:
- from:
kind: DockerImage
name: mbs-ui:latest
name: latest
- apiVersion: v1
kind: BuildConfig
metadata:
labels:
app: mbs-ui
name: mbs-ui
annotations:
template.alpha.openshift.io/wait-for-ready: "true"
spec:
output:
to:
kind: ImageStreamTag
name: mbs-ui:latest
postCommit: {}
resources: {}
runPolicy: Serial
source:
git:
ref: master
uri: https://github.com/release-engineering/mbs-ui
type: Git
strategy:
sourceStrategy:
env:
- name: NG_ENVIRONMENT
value: ${NG_ENVIRONMENT}
from:
kind: ImageStreamTag
name: s2i-angular-httpd24:angular5
type: Source
triggers:
- type: ConfigChange
- imageChange: {}
type: ImageChange
- apiVersion: v1
kind: DeploymentConfig
metadata:
labels:
app: mbs-ui
name: mbs-ui
annotations:
template.alpha.openshift.io/wait-for-ready: "true"
spec:
replicas: 1
selector:
app: mbs-ui
deploymentconfig: mbs-ui
template:
metadata:
labels:
app: mbs-ui
deploymentconfig: mbs-ui
spec:
containers:
- image: mbs-ui
imagePullPolicy: Always
name: mbs-ui
ports:
- containerPort: 8443
protocol: TCP
- containerPort: 8080
protocol: TCP
livenessProbe:
failureThreshold: 3
httpGet:
path: /modules
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 60
timeoutSeconds: 5
readinessProbe:
failureThreshold: 3
httpGet:
path: /modules
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 60
timeoutSeconds: 5
triggers:
- type: ConfigChange
- imageChangeParams:
automatic: true
containerNames:
- mbs-ui
from:
kind: ImageStreamTag
name: mbs-ui:latest
type: ImageChange
- apiVersion: v1
kind: Service
metadata:
labels:
app: mbs-ui
name: mbs-ui
spec:
ports:
- name: 8080-tcp
port: 8080
protocol: TCP
targetPort: 8080
- name: 8443-tcp
port: 8443
protocol: TCP
targetPort: 8443
selector:
app: mbs-ui
deploymentconfig: mbs-ui
- apiVersion: v1
kind: Route
metadata:
labels:
app: mbs-ui
name: mbs-ui
spec:
host: ${ROUTE_HOSTNAME}
port:
targetPort: 8080
to:
kind: Service
name: mbs-ui

59
mbs-ui/package.json Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "mbs",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"test:once": "ng test --watch=false",
"ci": "npm run lint && npm run test:once"
},
"private": true,
"dependencies": {
"@angular/animations": "^7.2.14",
"@angular/common": "^7.2.14",
"@angular/compiler": "^7.2.14",
"@angular/core": "^7.2.14",
"@angular/forms": "^7.2.14",
"@angular/http": "^7.2.14",
"@angular/platform-browser": "^7.2.14",
"@angular/platform-browser-dynamic": "^7.2.14",
"@angular/platform-server": "^7.2.14",
"@angular/router": "^7.2.14",
"@ng-bootstrap/ng-bootstrap": "^3.2.0",
"@ngx-progressbar/core": "^5.3.2",
"@ngx-progressbar/http": "^5.3.2",
"bootstrap": "^4.0.0",
"core-js": "^2.6.5",
"font-awesome": "^4.7.0",
"ngx-infinite-scroll": "^7.1.0",
"rxjs": "^6.3.3",
"zone.js": "^0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.13.8",
"@angular/cli": "^7.3.8",
"@angular/compiler-cli": "^7.2.14",
"@angular/language-service": "^7.2.14",
"@types/jasmine": "^3.3.12",
"@types/jasminewd2": "~2.0.2",
"@types/node": "^12.0.0",
"codelyzer": "^5.0.1",
"jasmine-core": "^3.4.0",
"jasmine-spec-reporter": "^4.2.1",
"karma": "^4.1.0",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^2.0.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "^5.4.2",
"rxjs-tslint": "^0.1.7",
"ts-node": "~7.0.0",
"tslint": "^5.16.0",
"typescript": ">=3.1.1 <3.3.0"
}
}

28
mbs-ui/protractor.conf.js Normal file
View File

@@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@@ -0,0 +1,39 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ModulesComponent } from './mbs/modules/modules.component';
import { ModuleComponentsComponent } from './mbs/module-components/module-components.component';
import { ModuleDetailComponent } from './mbs/module-detail/module-detail.component';
const routes: Routes = [
{
path: 'module/:id',
component: ModuleDetailComponent,
pathMatch: 'full'
},
{
path: 'modules',
component: ModulesComponent,
pathMatch: 'full'
},
{
path: 'components',
component: ModuleComponentsComponent,
pathMatch: 'full'
},
{
path: 'module/:id/components',
component: ModuleComponentsComponent,
pathMatch: 'full'
},
{
path: '',
redirectTo: '/modules',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1,30 @@
<ng-progress [thick]="true" [color]="'#cc0000'"></ng-progress>
<div class="container-fluid bg-dark">
<nav class="navbar navbar-dark bg-dark navbar-expand-lg">
<a class="navbar-brand" routerLink="/" (click)="navbarCollapsed = true" #navbarBrand>
<img src="./assets/mbs.png" width="32.5" height="32.5" alt="">
<span id="logoTextShort">MBS</span>
<span id="logoText">Module Build Service</span>
</a>
<button class="navbar-toggler navbar-toggler-right" type="button" (click)="navbarCollapsed = !navbarCollapsed"
[attr.aria-expanded]="!navbarCollapsed" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse" [ngbCollapse]="navbarCollapsed" id="navbarContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item" [routerLinkActive]="['active']">
<a class="nav-link" routerLink="/modules" (click)="navbarCollapsed = true">Modules</a>
</li>
<li class="nav-item" [routerLinkActive]="['active']">
<a class="nav-link" routerLink="/components" (click)="navbarCollapsed = true">Components</a>
</li>
</ul>
</div>
</nav>
</div>
<div class="container mt-5 mb-5">
<router-outlet></router-outlet>
</div>

View File

@@ -0,0 +1,27 @@
.navbar-brand span {
margin-left: 0.4em;
}
// Landscape phones and smaller
@media (max-width: 767px) {
#logoTextShort {
display: inline;
}
#logoText {
display: None;
}
}
// Tablets and larger
@media (min-width: 768px) {
.navbar {
padding-left: 10% !important;
padding-right: 10% !important;
}
#logoTextShort {
display: None;
}
#logoText {
display: inline;
}
}

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';
navbarCollapsed = true;
}

View File

@@ -0,0 +1,42 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { NgProgressModule } from '@ngx-progressbar/core';
import { NgProgressHttpModule } from '@ngx-progressbar/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ModulesComponent } from './mbs/modules/modules.component';
import { ModuleService } from './mbs/services/module.service';
import { ModuleComponentsComponent } from './mbs/module-components/module-components.component';
import { ModuleDetailComponent } from './mbs/module-detail/module-detail.component';
@NgModule({
declarations: [
AppComponent,
ModulesComponent,
ModuleComponentsComponent,
ModuleDetailComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
BrowserAnimationsModule,
NgbModule.forRoot(),
InfiniteScrollModule,
NgProgressModule,
NgProgressHttpModule
],
providers: [
ModuleService
],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,42 @@
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
export class BaseListComponent {
orderBy: string;
orderDirection: string;
currentPage: number;
loading = false;
exhausted = false;
processRouteParams(params: ParamMap) {
// TODO: Add validation here
if (params.get('orderBy')) {
this.orderBy = params.get('orderBy');
} else {
this.orderBy = 'id';
}
if (params.get('orderDirection')) {
this.orderDirection = params.get('orderDirection');
} else {
this.orderDirection = 'desc';
}
}
getOrderDirection(header): string {
if (header.toLowerCase().replace(' ', '_') === this.orderBy && this.orderDirection === 'desc') {
return 'asc';
}
return 'desc';
}
getArrowClass(header: string): string {
if (header.toLowerCase().replace(' ', '_') === this.orderBy) {
if (this.orderDirection === 'asc') {
return 'orderAsc';
} else {
return 'orderDesc';
}
}
return '';
}
}

View File

@@ -0,0 +1,56 @@
interface MbsMetaApi {
first: string;
last: string;
next: string | undefined;
page: number;
pages: number;
per_page: number;
prev: number | undefined;
total: number;
}
interface MbsApi {
items: Array<any>;
meta: MbsMetaApi;
}
export interface MbsModuleShort {
context?: string;
id: number;
name: string;
scratch?: boolean;
state: number;
state_name: string;
stream: string;
version: string;
}
export interface MbsModule extends MbsModuleShort {
koji_tag: string;
owner: string;
rebuild_strategy: string;
scmurl: string | undefined;
state_reason: string | undefined;
tasks: any;
time_completed: string | undefined;
time_modified: string | undefined;
time_submitted: string;
}
export interface MbsComponent {
id: number;
format: string;
module_build: number;
package: string;
state: number;
state_name: string;
task_id: number;
}
export interface MbsModulesShortApi extends MbsApi {
items: Array<MbsModuleShort>;
}
export interface MbsComponentsApi extends MbsApi {
items: Array<MbsComponent>;
}

View File

@@ -0,0 +1,32 @@
<h2 class="title">Components</h2>
<table class="table table-responsive-sm table-hover table-bordered mbs-list-table" infinite-scroll (scrolled)="onScrollDown()">
<thead>
<tr>
<th *ngFor="let header of ['ID', 'Module Build', 'Package', 'Task ID', 'State']" scope="col">
<a *ngIf="header != 'Module Build'" [routerLink]="['/components', {'orderBy': header.toLowerCase().replace(' ', '_'), 'orderDirection': getOrderDirection(header)}]"
[ngClass]="getArrowClass(header)">
{{ header }}
</a>
<span *ngIf="header == 'Module Build'">{{ header }}</span>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let component of components">
<td scope="row">{{ component.id }}</td>
<td scope="row"><a [routerLink]="['/module', component.module_build]">{{ component.module_build }}</a></td>
<td scope="row">{{ component.package }}</td>
<td scope="row">
<a href="{{ kojiUrl + 'taskinfo?taskID=' + component.task_id }}" target="_blank">
{{ component.task_id }}
</a>
</td>
<td class="{{ getStateCssClass(component) }}" scope="row">
<ng-container *ngIf="component.state_name">
{{ component.state_name.charAt(0) + component.state_name.toLowerCase().slice(1) }}
</ng-container>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,9 @@
table thead {
th {
width: 15%;
white-space: nowrap;
}
th:nth-child(3) {
width: 25%;
}
}

View File

@@ -0,0 +1,90 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { BaseListComponent } from '../base-components/base-list.component';
import { ModuleService } from '../services/module.service';
import { MbsComponent } from '../models/mbs.type';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-module-components',
templateUrl: './module-components.component.html',
styleUrls: ['./module-components.component.scss']
})
export class ModuleComponentsComponent extends BaseListComponent implements OnInit {
readonly kojiUrl: string = environment.kojiUrl;
components: Array<MbsComponent> = [];
moduleID: number;
constructor(
private route: ActivatedRoute,
private router: Router,
private moduleService: ModuleService
) {
super();
}
ngOnInit() {
this.route.paramMap.subscribe((params: ParamMap) => {
this.processRouteParams(params);
this.components = [];
this.exhausted = false;
this.currentPage = 1;
const routeParams = this.route.snapshot.params;
let moduleID = null;
if (routeParams.id) {
moduleID = parseInt(routeParams.id, 10);
if (isNaN(moduleID)) {
moduleID = null;
// Redirect to /components if moduleID is not a number
this.router.navigate(['/components']);
}
}
this.moduleID = moduleID;
this.getComponents();
});
}
getComponents(): void {
if (!this.exhausted && !this.loading) {
this.loading = true;
this.moduleService.getComponents(this.currentPage, this.orderBy, this.orderDirection, this.moduleID).subscribe(
data => {
if (data.items.length) {
this.components = this.components.concat(data.items);
this.currentPage += 1;
} else {
this.exhausted = true;
}
},
error => {
console.error(error);
},
() => {
this.loading = false;
}
);
}
}
getStateCssClass(component: MbsComponent): string {
switch (component.state_name) {
case 'COMPLETE':
return 'text-success';
case 'FAILED':
return 'text-danger';
case 'CANCELED':
case 'DELETED':
return 'text-warning';
default :
return 'text-info';
}
}
onScrollDown () {
this.getComponents();
}
}

View File

@@ -0,0 +1,113 @@
<span *ngIf="module">
<h2 class="title">Module #{{ module.id }}</h2>
<!-- Tracker if the build has failed -->
<ul *ngIf="module.state_name == 'failed' || module.state_name == 'garbage'" class="build-tracker build-tracker-failed">
<li>Init</li>
<li>Wait</li>
<li>Build</li>
<li>Done</li>
<li>{{ module.state_name.charAt(0).toUpperCase() + module.state_name.slice(1) }}</li>
</ul>
<!-- Tracker if the build has succeeded -->
<ul *ngIf="module.state_name != 'failed' && module.state_name != 'garbage'" class="build-tracker">
<li [ngClass]="{'build-tracker-step-done': module.state > 0, 'build-tracker-step-active': module.state == 0}">Init</li>
<li [ngClass]="{'build-tracker-step-done': module.state > 1, 'build-tracker-step-active': module.state == 1}">Wait</li>
<li [ngClass]="{'build-tracker-step-done': module.state > 2, 'build-tracker-step-active': module.state == 2}">Build</li>
<li [ngClass]="{
'build-tracker-step-done': module.state > 3 || (module.state == 3 && module.scratch),
'build-tracker-step-active': module.state == 3 && !module.scratch
}">Done</li>
<li [ngClass]="{'build-tracker-step-done': module.state >= 4}">Ready</li>
</ul>
<table class="table table-hover table-bordered">
<thead>
<tr>
<th scope="col">Property</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">Components</td>
<td scope="row">
<a [routerLink]="['/module', module.id, 'components']">
{{ num_built_components }}/{{ num_components }} complete
</a>
</td>
</tr>
<tr>
<td scope="row">Context</td>
<td scope="row">{{ module.context }}</td>
</tr>
<tr>
<td scope="row">ID</td>
<td scope="row">{{ module.id }}</td>
</tr>
<tr>
<td scope="row">Koji Build Tag</td>
<td scope="row">
<a *ngIf="module.koji_tag" target="_blank" href="{{ getKojiTagUrl(module.koji_tag + '-build') }}">
{{ module.koji_tag + '-build' }}
</a>
</td>
</tr>
<tr>
<td scope="row">Koji Tag</td>
<td scope="row">
<a *ngIf="module.koji_tag" target="_blank" href="{{ getKojiTagUrl(module.koji_tag) }}">
{{ module.koji_tag }}
</a>
</td>
</tr>
<tr>
<td scope="row">Name</td>
<td scope="row">{{ module.name }}</td>
</tr>
<tr>
<td scope="row">NSVC</td>
<td scope="row">{{ module.name }}:{{ module.stream }}:{{ module.version }}:{{ module.context }}</td>
</tr>
<tr>
<td scope="row">Owner</td>
<td scope="row">{{ module.owner }}</td>
</tr>
<tr>
<td scope="row">Rebuild Strategy</td>
<td scope="row">{{ module.rebuild_strategy }}</td>
</tr>
<tr>
<td scope="row">Scratch</td>
<td scope="row">{{ module.scratch ? 'Yes' : 'No' }}</td>
</tr>
<tr>
<td scope="row">State</td>
<td scope="row">
{{ module.state_name.toUpperCase().charAt(0) + module.state_name.slice(1) }}
</td>
</tr>
<tr>
<td scope="row">State Reason</td>
<td scope="row">{{ module.state_reason }}</td>
</tr>
<tr>
<td scope="row">Stream</td>
<td scope="row">{{ module.stream }}</td>
</tr>
<tr *ngFor="let time of ['time_completed', 'time_modified', 'time_submitted']">
<td scope="row">{{ time.replace('_', ' ') | titlecase }}</td>
<td scope="row">
<!-- Show in local time on hover in a tooltip -->
<span *ngIf="module[time]" placement="top" ngbTooltip="{{ module[time] | date:'MMMM d, y, HH:mm:ss zzzz' }}" class="timeValue">
{{ module[time] | date:'MMMM d, y, HH:mm:ss':'+0000' }} UTC
</span>
</td>
</tr>
<tr>
<td scope="row">Version</td>
<td scope="row">{{ module.version }}</td>
</tr>
</tbody>
</table>
</span>

View File

@@ -0,0 +1,107 @@
table tbody td:first-child {
width: 30%;
}
.timeValue {
text-decoration: underline;
text-decoration-style: dashed;
cursor: help;
}
ul.build-tracker {
// Set the padding so the list takes up all the content in the DOM that
// it graphically represents
padding: 0 0 65px 0;
// Provide adequate spacing between the title and table
margin: 1rem 0 1.5rem 0;
li {
// This number multiplied by the number of list items must equal to
// 100% so it can take up the whole container
width: 20%;
list-style-type: none;
// Makes the list look inline
float: left;
position: relative;
// Align the text with the icons
text-align: center;
}
li:before {
font-family: FontAwesome;
content: '\f10c';
font-size: 30px;
color: #28a745;
line-height: 0.8;
// Hide the line behind it
background-color: #ffffff;
// the size is tweaked so that it is slightly smaller then the icon
// size so the pulse doesn't show any white
width: 25px;
height: 23px;
// Make the icon act as a block so that the text is displayed below
// it and not next to it
display: block;
// Center the icon and have the text 5 px below it
margin: 0 auto 5px;
}
li:after {
content: '';
position: absolute;
width: 100%;
height: 3px;
background-color: #343a40;
top: 12px;
left: -50%;
// Put it behind the symbol
z-index: -1;
}
// Tablets and larger
@media (min-width: 767px) {
padding: 10px 0 80px 0;
margin: 3rem 0 3rem 0;
li:before {
font-size: 50px;
width: 42.5px;
height: 40px;
line-height: 0.8;
margin: 0 auto 15px;
}
li:after {
top: 20px;
}
}
li:first-child:after {
content: none;
}
li.build-tracker-step-done:before {
content: '\f05d';
}
li.build-tracker-step-active:before {
// Settings so that we can pulse the icon
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7);
animation: pulse 1.25s infinite cubic-bezier(0.66, 0, 0, 1);
}
}
ul.build-tracker-failed {
li:before {
color: #cc0000;
content: '\f05c';
}
}
// Pulse the active icon
@keyframes pulse {to {box-shadow: 0 0 0 15px rgba(204, 0, 0, 0);}}
// Tablets and larger
@media (min-width: 767px) {
@keyframes pulse {to {box-shadow: 0 0 0 25px rgba(204, 0, 0, 0);}}
}

View File

@@ -0,0 +1,66 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ModuleService } from '../services/module.service';
import { MbsModule } from '../models/mbs.type';
import { environment } from '..//../../environments/environment';
@Component({
selector: 'app-module-detail',
templateUrl: './module-detail.component.html',
styleUrls: ['./module-detail.component.scss']
})
export class ModuleDetailComponent implements OnInit, OnDestroy {
module: MbsModule;
interval: any;
num_built_components: number;
num_components: number;
readonly dateTimeFormat: string = 'MMMM d, y, HH:mm:ss zzzz';
readonly kojiUrl: string = environment.kojiUrl;
constructor(private route: ActivatedRoute, private moduleService: ModuleService) { }
ngOnInit() {
this.getModule();
// Run it every 15 seconds
this.interval = setInterval(() => { this.getModule(); }, 15000);
}
ngOnDestroy() {
clearInterval(this.interval);
}
getModule(): void {
// Don't reload the page when the build has failed or completed
if (!this.module || (this.module.state_name !== 'failed' && this.module.state_name !== 'ready')) {
const moduleObservable: Observable<MbsModule> = this.route.paramMap.pipe(switchMap(
(params: ParamMap) => this.moduleService.getModule(+params.get('id'))));
moduleObservable.subscribe(
data => {
this.module = data;
let num_built_components = 0;
let num_components = 0;
for (const component of Object.keys(this.module.tasks.rpms)) {
num_components += 1;
if (this.module.tasks.rpms[component].state === 1) {
num_built_components += 1;
}
}
this.num_built_components = num_built_components;
this.num_components = num_components;
},
error => {
console.error(error);
}
);
}
}
getKojiTagUrl(tag: string): string {
// encodeURI doesn't convert plus signs to $%2B, so this does this manually
return `${this.kojiUrl}search?match=exact&type=tag&terms=${encodeURI(tag).replace('+', '%2B')}`;
}
}

View File

@@ -0,0 +1,30 @@
<h2 class="title">Modules</h2>
<table class="table table-responsive-sm table-hover table-bordered mbs-list-table" infinite-scroll (scrolled)="onScrollDown()">
<thead>
<tr>
<th *ngFor="let header of ['ID', 'Name', 'Stream', 'Version', 'Context', 'State']"
[attr.id]="header.toLowerCase() + 'Header'" scope="col">
<a *ngIf="header != 'Context'" [routerLink]="['/modules', {'orderBy': header.toLowerCase(), 'orderDirection': getOrderDirection(header)}]"
[ngClass]="getArrowClass(header)">
{{ header }}
</a>
<ng-container *ngIf="header === 'Context'">
{{ header }}
</ng-container>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let module of modules">
<td scope="row"><a [routerLink]="['/module', module.id]">{{ module.id }}</a></td>
<td scope="row">{{ module.name + (module.scratch ? ' (scratch)' : '') }}</td>
<td scope="row">{{ module.stream }}</td>
<td scope="row">{{ module.version }}</td>
<td *ngIf="module.context !== undefined" scope="row" class="context">{{ module.context }}</td>
<td class="{{ getStateCssClass(module) }}" scope="row">
{{ module.state_name.toUpperCase().charAt(0) + module.state_name.slice(1) }}
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,19 @@
table thead {
th {
width: 20%;
white-space: nowrap;
}
#nameHeader {
width:30%;
}
}
// Landscape phones and smaller
@media (max-width: 767px) {
table {
// Don't display the context column on larger screens
#contextHeader, td.context {
display: none;
}
}
}

View File

@@ -0,0 +1,70 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { BaseListComponent } from '../base-components/base-list.component';
import { ModuleService } from '../services/module.service';
import { MbsModuleShort } from '../models/mbs.type';
@Component({
selector: 'app-modules',
templateUrl: './modules.component.html',
styleUrls: ['./modules.component.scss'],
})
export class ModulesComponent extends BaseListComponent implements OnInit {
modules: Array<MbsModuleShort>;
constructor(private route: ActivatedRoute,
private moduleService: ModuleService) { super(); }
ngOnInit() {
this.route.paramMap.subscribe((params: ParamMap) => {
this.processRouteParams(params);
this.modules = [];
this.exhausted = false;
this.currentPage = 1;
this.getModules();
});
}
getModules(): void {
if (!this.exhausted && !this.loading) {
this.loading = true;
this.moduleService.getModules(this.currentPage, this.orderBy, this.orderDirection).subscribe(
data => {
if (data.items.length) {
this.modules = this.modules.concat(data.items);
this.currentPage += 1;
} else {
this.exhausted = true;
}
},
error => {
console.error(error);
},
() => {
this.loading = false;
}
);
}
}
getStateCssClass(module: MbsModuleShort): string {
switch (module.state_name) {
case 'ready': {
return 'text-success';
}
case 'failed': {
return 'text-danger';
}
default : {
return 'text-info';
}
}
}
onScrollDown () {
this.getModules();
}
}

View File

@@ -0,0 +1,118 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ModuleService } from 'mbs/services/module.service';
import { MbsModulesShortApi } from '../models/mbs.type';
describe('ModuleService', () => {
let service: ModuleService;
let httpMock: HttpTestingController;
const mockModulesShell: MbsModulesShortApi = {
items: [],
meta: {
first: null,
last: null,
next: null,
page: 1,
pages: 1,
per_page: 40,
prev: null,
total: 0
}
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ModuleService]
});
service = TestBed.get(ModuleService);
httpMock = TestBed.get(HttpTestingController);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get a list of modules', () => {
const mockModules: MbsModulesShortApi = Object.assign({}, mockModulesShell);
mockModules.meta.total = 4;
mockModules.items = [
{
'context': 'c2c572ec',
'id': 1547,
'name': 'testmodule',
'state': 5,
'state_name': 'ready',
'stream': 'master',
'version': '20180224011125'
},
{
'context': '00000000',
'id': 1546,
'name': 'mongodb',
'state': 4,
'state_name': 'failed',
'stream': 'private_mskalick_test',
'version': '20171120143335'
},
{
'context': '5332648f',
'id': 1545,
'name': 'perl',
'state': 5,
'state_name': 'ready',
'stream': '5.26',
'version': '20180220133547'
},
{
'context': '3cc1cf22',
'id': 1544,
'name': 'perl',
'state': 5,
'state_name': 'ready',
'stream': '5.24',
'version': '20180220083157'
}
];
// Default arguments
service.getModules().subscribe(modules => {
expect(modules.items.length).toBe(4);
});
const req = httpMock.expectOne(`${service.mbsUrl}module-builds/?short=true&per_page=40&page=1&order_desc_by=id`);
expect(req.request.method).toEqual('GET');
// Respond with mock data
req.flush(mockModules);
// Assert that there are no outstanding requests
httpMock.verify();
});
it('should ask for list of modules ordered by name and ascending', () => {
// Order by name in ascending order
service.getModules(1, 'name', 'asc').subscribe(modules => {
expect(modules.items.length).toBe(0);
});
const req = httpMock.expectOne(`${service.mbsUrl}module-builds/?short=true&per_page=40&page=1&order_by=name`);
expect(req.request.method).toEqual('GET');
// Respond with mock data
req.flush(mockModulesShell);
// Assert that there are no outstanding requests
httpMock.verify();
});
it('should ask for list of modules on page 2', () => {
const mockModules: MbsModulesShortApi = Object.assign({}, mockModulesShell);
mockModules.meta.page = 2;
// Order by name in ascending order
service.getModules(2).subscribe(modules => {
expect(modules.items.length).toBe(0);
});
const req = httpMock.expectOne(`${service.mbsUrl}module-builds/?short=true&per_page=40&page=2&order_desc_by=id`);
expect(req.request.method).toEqual('GET');
// Respond with mock data
req.flush(mockModules);
// Assert that there are no outstanding requests
httpMock.verify();
});
});

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { MbsModulesShortApi, MbsModule, MbsComponentsApi, MbsComponent } from '../models/mbs.type';
@Injectable()
export class ModuleService {
readonly mbsUrl: string = environment.mbsUrl;
constructor(private http: HttpClient) { }
private getOrderKey(orderDirection: string): string {
if (orderDirection === 'desc') {
return 'order_desc_by';
} else {
return 'order_by';
}
}
getModules(page: number = 1, orderBy: string = 'id', orderDirection: string = 'desc'): Observable<MbsModulesShortApi> {
const orderKey = this.getOrderKey(orderDirection);
const url = this.mbsUrl + 'module-builds/?short=true&per_page=40&page=' + page + '&' + orderKey + '=' + orderBy;
return this.http.get<MbsModulesShortApi>(url);
}
getModule(id: number): Observable<MbsModule> {
return this.http.get<MbsModule>(this.mbsUrl + 'module-builds/' + id);
}
getComponents(page: number = 1, orderBy: string = 'id', orderDirection: string = 'desc', moduleID: number = null):
Observable<MbsComponentsApi> {
const orderKey = this.getOrderKey(orderDirection);
let url = `${this.mbsUrl}component-builds/?per_page=40&page=${page}&orderKey=${orderBy}`;
if (moduleID !== null) {
url += `&module_id=${moduleID}`;
}
return this.http.get<MbsComponentsApi>(url);
}
getComponent(id: number): Observable<MbsComponent> {
return this.http.get<MbsComponent>(this.mbsUrl + 'component-builds/' + id);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="./assets/mstile-150x150.png"/>
<TileColor>#2b5797</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
mbs-ui/src/assets/mbs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,14 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "./assets/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -0,0 +1,5 @@
export const environment = {
production: true,
mbsUrl: 'https://mbs.fedoraproject.org/module-build-service/1/',
kojiUrl: 'https://koji.fedoraproject.org/koji/'
};

View File

@@ -0,0 +1,5 @@
export const environment = {
production: true,
mbsUrl: 'https://mbs.engineering.redhat.com/module-build-service/1/',
kojiUrl: 'https://brewweb.engineering.redhat.com/brew/'
};

View File

@@ -0,0 +1,10 @@
// 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,
mbsUrl: 'https://mbs.fedoraproject.org/module-build-service/1/',
kojiUrl: 'https://koji.fedoraproject.org/koji/'
};

20
mbs-ui/src/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Module Build Service</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="./assets/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/favicon-16x16.png">
<link rel="manifest" href="./assets/site.webmanifest">
<link rel="shortcut icon" href="./assets/favicon.ico">
<meta name="msapplication-TileColor" content="#2b5797">
<meta name="msapplication-config" content="./assets/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<app-root></app-root>
</body>
</html>

12
mbs-ui/src/main.ts Normal file
View File

@@ -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));

76
mbs-ui/src/polyfills.ts Normal file
View File

@@ -0,0 +1,76 @@
/**
* 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`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.
/**
* Need to import at least one locale-data with intl.
*/
// import 'intl/locale-data/jsonp/en';

47
mbs-ui/src/styles.scss Normal file
View File

@@ -0,0 +1,47 @@
@import "~bootstrap/scss/bootstrap";
$fa-font-path: "../node_modules/font-awesome/fonts";
@import '~font-awesome/scss/font-awesome.scss';
.title {
text-align: center;
margin-bottom: 1.5rem;
}
// Tablets and larger
@media (min-width: 767px) {
.title {
margin-bottom: 2.5rem;
}
}
table.mbs-list-table thead {
th {
background-color: #343a40 !important;
border-color: #151515;
color: #FDFDFD;
a {
text-decoration: none;
color: #FDFDFD;
display:inline-block;
}
a:hover {
text-decoration: underline;
}
a.orderAsc:after {
font-family: FontAwesome;
content: '\f077';
font-weight: lighter;
display:inline-block;
}
a.orderDesc:after {
font-family: FontAwesome;
content: '\f078';
font-weight: lighter;
display:inline-block;
}
}
}

32
mbs-ui/src/test.ts Normal file
View File

@@ -0,0 +1,32 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare const __karma__: any;
declare const require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,21 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

5
mbs-ui/src/typings.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

21
mbs-ui/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"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"
],
"module": "es2015",
"baseUrl": "./"
}
}

140
mbs-ui/tslint.json Normal file
View File

@@ -0,0 +1,140 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"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": true,
"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"
}
],
"typeof-compare": true,
"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"
],
"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,
"invoke-injectable": true
}
}