dashboard: make ZuulService load info api to manage paths
To fix multi-tenants dashboard, this change: * Makes ZuulService a single-service * Loads api/info endpoint onInit and use the returned value in getsourceUrl * Adapts all component to wait for ZuulService and remove local tenant params * Changes navigation component to not display navigations bar in tenants list * Changes navigation to disable dashboard link in white-label setup * Adds an index.html file Change-Id: I7406935c236d61b1ac884f1e69666d488e36844c
This commit is contained in:
parent
c5a6dc6186
commit
a82312a490
|
@ -61,6 +61,10 @@ const appRoutes: Routes = [
|
|||
path: 'tenants.html',
|
||||
component: TenantsComponent
|
||||
},
|
||||
{
|
||||
path: 't/tenants.html',
|
||||
component: TenantsComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: StatusComponent
|
||||
|
|
|
@ -22,6 +22,8 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||
import { HttpClientModule } from '@angular/common/http'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
|
||||
import { CoreModule } from './core/core.module'
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { AppComponent } from './app.component'
|
||||
import { getAppBaseHref } from './zuul/zuul.service'
|
||||
|
@ -40,6 +42,7 @@ import ZuulService from './zuul/zuul.service'
|
|||
BrowserModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
CoreModule.forRoot({}),
|
||||
AppRoutingModule,
|
||||
],
|
||||
declarations: [
|
||||
|
|
|
@ -30,16 +30,14 @@ export default class BuildsComponent implements OnInit {
|
|||
pipeline: string
|
||||
job_name: string
|
||||
project: string
|
||||
tenant: string
|
||||
|
||||
constructor(
|
||||
private http: HttpClient, private route: ActivatedRoute,
|
||||
private zuul: ZuulService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
this.tenant = this.route.snapshot.paramMap.get('tenant')
|
||||
async ngOnInit() {
|
||||
await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
|
||||
|
||||
this.pipeline = this.route.snapshot.queryParamMap.get('pipeline')
|
||||
this.job_name = this.route.snapshot.queryParamMap.get('job_name')
|
||||
|
@ -54,18 +52,20 @@ export default class BuildsComponent implements OnInit {
|
|||
if (this.job_name) { params = params.set('job_name', this.job_name) }
|
||||
if (this.project) { params = params.set('project', this.project) }
|
||||
|
||||
const remoteLocation = this.zuul.getSourceUrl('builds', this.tenant)
|
||||
this.http.get<Build[]>(remoteLocation, {params: params})
|
||||
.subscribe(builds => {
|
||||
for (const build of builds) {
|
||||
/* Fix incorect url for post_failure job */
|
||||
/* TODO(mordred) Maybe let's fix this server side? */
|
||||
if (build.log_url === build.job_name) {
|
||||
build.log_url = undefined
|
||||
const remoteLocation = this.zuul.getSourceUrl('builds')
|
||||
if (remoteLocation) {
|
||||
this.http.get<Build[]>(remoteLocation, {params: params})
|
||||
.subscribe(builds => {
|
||||
for (const build of builds) {
|
||||
/* Fix incorect url for post_failure job */
|
||||
/* TODO(mordred) Maybe let's fix this server side? */
|
||||
if (build.log_url === build.job_name) {
|
||||
build.log_url = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
this.builds = builds
|
||||
})
|
||||
this.builds = builds
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getRowClass(build: Build): string {
|
||||
|
|
|
@ -39,6 +39,11 @@ module.exports = {
|
|||
// Each of the entries below lists a specific 'chunk' which is one of the
|
||||
// entry items from above. We can collapse this to just do one single
|
||||
// output file.
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'web/config/main.ejs',
|
||||
title: 'Zuul Status'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'status.html',
|
||||
template: 'web/config/main.ejs',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'
|
||||
|
||||
import { CommonModule } from '@angular/common'
|
||||
|
||||
import { ZuulService } from '../zuul/zuul.service'
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
providers: [ ZuulService ]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (parentModule) {
|
||||
throw new Error(
|
||||
'CoreModule is already loaded. Import it in the AppModule only')
|
||||
}
|
||||
}
|
||||
|
||||
static forRoot(config: {}): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreModule,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,21 +26,23 @@ import Job from './job'
|
|||
export default class JobsComponent implements OnInit {
|
||||
|
||||
jobs: Job[]
|
||||
tenant?: string
|
||||
|
||||
constructor(
|
||||
private http: HttpClient, private route: ActivatedRoute,
|
||||
private zuul: ZuulService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.tenant = this.route.snapshot.paramMap.get('tenant')
|
||||
async ngOnInit() {
|
||||
await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
|
||||
this.jobsFetch()
|
||||
}
|
||||
|
||||
jobsFetch(): void {
|
||||
this.http.get<Job[]>(this.zuul.getSourceUrl('jobs', this.tenant))
|
||||
.subscribe(jobs => this.injestJobs(jobs))
|
||||
const remoteLocation = this.zuul.getSourceUrl('jobs')
|
||||
if (remoteLocation) {
|
||||
this.http.get<Job[]>(remoteLocation)
|
||||
.subscribe(jobs => this.injestJobs(jobs))
|
||||
}
|
||||
}
|
||||
|
||||
injestJobs(jobs: Job[]): void {
|
||||
|
|
|
@ -16,10 +16,11 @@ under the License.
|
|||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" [routerLink]="zuul.appBaseHref" target="_self">Zuul Dashboard</a>
|
||||
<a *ngIf="zuul.info && !zuul.info.whiteLabel" class="navbar-brand" [routerLink]="dashboardLink" target="_self">Zuul Dashboard</a>
|
||||
<span *ngIf="zuul.info && zuul.info.whiteLabel" class="navbar-brand">Zuul Dashboard</span>
|
||||
</div>
|
||||
<ul class="nav navbar-nav" *ngIf="showNavbar">
|
||||
<li routerLinkActive="active" *ngFor="let route of navbarRoutes">
|
||||
<ul class="nav navbar-nav" *ngIf="zuul.info && zuul.info.tenant !== ''">
|
||||
<li routerLinkActive="active" *ngFor="let route of zuul.navbarRoutes">
|
||||
<a [routerLink]="route.url">
|
||||
{{ route.title }}
|
||||
</a>
|
||||
|
|
|
@ -19,57 +19,16 @@ import { filter } from 'rxjs/operators'
|
|||
|
||||
import ZuulService from '../zuul/zuul.service'
|
||||
|
||||
import RouteDescription from './description'
|
||||
|
||||
@Component({
|
||||
selector: 'navigation',
|
||||
template: require('./navigation.component.html')
|
||||
})
|
||||
export default class NavigationComponent implements OnInit {
|
||||
navbarRoutes: RouteDescription[]
|
||||
resolveEnd$: Observable<ResolveEnd>
|
||||
showNavbar = true
|
||||
|
||||
private routePages = ['status', 'jobs', 'builds']
|
||||
dashboardLink: string
|
||||
|
||||
constructor(private router: Router, private zuul: ZuulService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.resolveEnd$ = this.router.events.pipe(
|
||||
filter(evt => evt instanceof ResolveEnd)
|
||||
) as Observable<ResolveEnd>
|
||||
this.resolveEnd$.subscribe(evt => {
|
||||
this.showNavbar = (evt.url !== '/tenants.html')
|
||||
this.navbarRoutes = this.getNavbarRoutes(evt.url)
|
||||
})
|
||||
}
|
||||
|
||||
getNavbarRoutes(url: string): RouteDescription[] {
|
||||
const routes = []
|
||||
for (const routePage of this.routePages) {
|
||||
const description: RouteDescription = {
|
||||
title: this.getRouteTitle(routePage),
|
||||
url: this.getRouterLink(routePage, url)
|
||||
}
|
||||
routes.push(description)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
getRouteTitle(target: string): string {
|
||||
return target.charAt(0).toUpperCase() + target.slice(1)
|
||||
}
|
||||
|
||||
getRouterLink(target: string, url: string): string[] {
|
||||
const htmlTarget = target + '.html'
|
||||
// NOTE: This won't work if there is a tenant name with a / in it. It would
|
||||
// be great to pull the tenant name from the paramMap- but so far I can't
|
||||
// find a way to get to the current paramMap of the current component from
|
||||
// inside of this component.
|
||||
if (url.startsWith('/t/')) {
|
||||
return ['/t', url.split('/')[2], htmlTarget]
|
||||
} else {
|
||||
return ['/' + htmlTarget]
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.dashboardLink = '/t/tenants.html'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,10 +36,12 @@ export default class StatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(private route: ActivatedRoute, private zuul: ZuulService) {}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
|
||||
|
||||
if (typeof this.app === 'undefined') {
|
||||
this.app = zuulStart(
|
||||
jQuery, this.route.snapshot.paramMap.get('tenant'), this.zuul)
|
||||
jQuery, this.zuul)
|
||||
}
|
||||
this.app.options.enabled = true
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import './jquery.zuul'
|
|||
/**
|
||||
* @return The $.zuul instance
|
||||
*/
|
||||
function zuulStart ($, tenant, zuulService) {
|
||||
function zuulStart ($, zuulService) {
|
||||
// Start the zuul app (expects default dom)
|
||||
|
||||
let $container, $indicator
|
||||
|
@ -50,7 +50,7 @@ function zuulStart ($, tenant, zuulService) {
|
|||
params['source_data'] = DemoStatusTree
|
||||
}
|
||||
} else {
|
||||
params['source'] = zuulService.getSourceUrl('status', tenant)
|
||||
params['source'] = zuulService.getSourceUrl('status')
|
||||
}
|
||||
|
||||
let zuul = $.zuul(params, zuulService)
|
||||
|
|
|
@ -34,7 +34,8 @@ export default class StreamComponent implements OnInit {
|
|||
|
||||
constructor(private route: ActivatedRoute, private zuul: ZuulService) {}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
|
||||
this.startStream()
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,6 @@ export default class StreamComponent implements OnInit {
|
|||
}, pageUpdateInMS)
|
||||
|
||||
const queryParamMap = this.route.snapshot.queryParamMap
|
||||
const tenant = this.route.snapshot.paramMap.get('tenant')
|
||||
|
||||
const params = {
|
||||
uuid: queryParamMap.get('uuid')
|
||||
|
@ -68,8 +68,7 @@ export default class StreamComponent implements OnInit {
|
|||
} else if (queryParamMap.has('websocket_url')) {
|
||||
params['websocket_url'] = queryParamMap.get('websocket_url')
|
||||
} else {
|
||||
params['websocket_url'] = this.zuul.getWebsocketUrl(
|
||||
'console-stream', tenant)
|
||||
params['websocket_url'] = this.zuul.getWebsocketUrl('console-stream')
|
||||
}
|
||||
const ws = new WebSocket(params['websocket_url'])
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ export default class TenantsComponent implements OnInit {
|
|||
|
||||
constructor(private http: HttpClient, private zuul: ZuulService) {}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
await this.zuul.setTenant()
|
||||
this.tenantsFetch()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2018 Red Hat
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
export default class Info {
|
||||
tenant: string
|
||||
whiteLabel: boolean
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2018 Red Hat
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Info from './info'
|
||||
|
||||
export default class InfoResponse {
|
||||
info: Info
|
||||
}
|
|
@ -12,11 +12,17 @@
|
|||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { Injectable, EventEmitter, Output } from '@angular/core'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Router } from '@angular/router'
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject'
|
||||
|
||||
import * as url from 'url'
|
||||
|
||||
import Info from './info'
|
||||
import InfoResponse from './infoResponse'
|
||||
import RouteDescription from './description'
|
||||
|
||||
declare var ZUUL_API_URL: string
|
||||
declare var ZUUL_BASE_HREF: string
|
||||
|
||||
|
@ -48,17 +54,39 @@ export function getAppBaseHref (): string {
|
|||
return path
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
class ZuulService {
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ZuulService {
|
||||
public baseApiUrl: string
|
||||
public appBaseHref: string
|
||||
public info: Info
|
||||
navbarRoutes: RouteDescription[]
|
||||
private routePages = ['status', 'jobs', 'builds']
|
||||
|
||||
constructor() {
|
||||
constructor(private router: Router, private http: HttpClient) {
|
||||
this.baseApiUrl = this.getBaseApiUrl()
|
||||
this.appBaseHref = getAppBaseHref()
|
||||
}
|
||||
|
||||
async setTenant (tenant?: string) {
|
||||
if (!this.info) {
|
||||
const infoEndpoint = this.baseApiUrl + 'api/info'
|
||||
const infoResponse = await this.http.get<InfoResponse>(
|
||||
infoEndpoint).toPromise()
|
||||
this.info = infoResponse.info
|
||||
if (this.info.tenant && !tenant) {
|
||||
this.info.whiteLabel = true
|
||||
} else {
|
||||
this.info.whiteLabel = false
|
||||
}
|
||||
}
|
||||
if (tenant) {
|
||||
this.info.tenant = tenant
|
||||
}
|
||||
this.navbarRoutes = this.getNavbarRoutes()
|
||||
}
|
||||
|
||||
getBaseApiUrl (): string {
|
||||
let path
|
||||
if (typeof ZUUL_API_URL !== 'undefined') {
|
||||
|
@ -72,22 +100,53 @@ class ZuulService {
|
|||
return path
|
||||
}
|
||||
|
||||
getSourceUrl (filename: string, tenant?: string): string {
|
||||
if (tenant) {
|
||||
// Multi-tenant deploy. This is at t/a-tenant/x.html
|
||||
return url.resolve(this.baseApiUrl, `api/tenant/${tenant}/${filename}`)
|
||||
} else {
|
||||
// Whitelabel deploy or tenants list, such as /status.html,
|
||||
// /tenants.html or /zuul/status.html or /zuul/tenants.html
|
||||
getSourceUrl (filename: string): string {
|
||||
const tenant = this.info.tenant
|
||||
if (this.info.whiteLabel || filename === 'tenants') {
|
||||
if (!this.info.whiteLabel) {
|
||||
// Reset selected tenant
|
||||
this.info.tenant = ''
|
||||
}
|
||||
return url.resolve(this.baseApiUrl, `api/${filename}`)
|
||||
}
|
||||
if (!tenant) {
|
||||
// No tenant selected, go to tenant list
|
||||
console.log('No tenant selected, navigate to tenants list')
|
||||
this.router.navigate(['/tenants.html'])
|
||||
}
|
||||
return url.resolve(this.baseApiUrl, `api/tenant/${tenant}/${filename}`)
|
||||
}
|
||||
|
||||
getWebsocketUrl (filename: string, tenant?: string): string {
|
||||
return this.getSourceUrl(filename, tenant)
|
||||
getWebsocketUrl (filename: string): string {
|
||||
return this.getSourceUrl(filename)
|
||||
.replace(/(http)(s)?\:\/\//, 'ws$2://')
|
||||
}
|
||||
|
||||
getNavbarRoutes(): RouteDescription[] {
|
||||
const routes = []
|
||||
for (const routePage of this.routePages) {
|
||||
const description: RouteDescription = {
|
||||
title: this.getRouteTitle(routePage),
|
||||
url: this.getRouterLink(routePage)
|
||||
}
|
||||
routes.push(description)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
getRouteTitle(target: string): string {
|
||||
return target.charAt(0).toUpperCase() + target.slice(1)
|
||||
}
|
||||
|
||||
getRouterLink(target: string): string[] {
|
||||
const htmlTarget = target + '.html'
|
||||
if (this.info.whiteLabel) {
|
||||
return ['/' + htmlTarget]
|
||||
} else {
|
||||
return ['/t', this.info.tenant, htmlTarget]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ZuulService
|
||||
|
|
Loading…
Reference in New Issue