Permalink
Browse files

feat(ui): image builder

  • Loading branch information...
jkuri committed Sep 19, 2017
1 parent c03c1ce commit 59340436d65712caa72b23e85defa59aed7c15e6
@@ -17,22 +17,36 @@ export interface ImageBuildOutput {
output: any;
}

export function buildDockerImage(data: ImageData): Observable<ImageBuildOutput> {
return new Observable(observer => {
prepareDirectory(data).then(() => {
const folderPath = getFilePath(`images/${data.name}`);
const src = glob.sync(folderPath + '/**/*').map(filePath => filePath.split('/').pop());

docker.buildImage({ context: folderPath, src: src }, { t: data.name })
.then(output => {
output.on('data', d => observer.next({ name: data.name, output: d.toString() }));
output.on('finish', () => observer.complete());
})
.catch(err => {
observer.error(err);
observer.complete();
export const imageBuilder: Subject<ImageBuildOutput> = new Subject();
imageBuilder.share();

export function buildDockerImage(data: ImageData): void {
prepareDirectory(data).then(() => {
const folderPath = getFilePath(`images/${data.name}`);
const src = glob.sync(folderPath + '/**/*').map(filePath => filePath.split('/').pop());

let msg: LogMessageType = {
message: `starting image build ${data.name}`,
type: 'info',
notify: false
};
logger.next(msg);

docker.buildImage({ context: folderPath, src: src }, { t: data.name })
.then(output => {
output.on('data', d => {
imageBuilder.next({ name: data.name, output: d.toString() });
});
});
output.on('finish', () => {
msg.message = `image ${data.name} build successfully completed`;
logger.next(msg);
});
})
.catch(err => {
msg.message = `error while building image ${data.name} (${err})`;
msg.type = 'error';
logger.next(msg);
});
});
}

@@ -17,7 +17,7 @@ import {
terminalEvents,
getJobProcesses
} from './process-manager';
import { buildDockerImage } from './image-builder';
import { imageBuilder, buildDockerImage } from './image-builder';
import { getConfig } from './utils';
import * as https from 'https';
import * as http from 'http';
@@ -112,13 +112,15 @@ export class SocketServer {

case 'buildImage': {
const imageData = event.data;
buildDockerImage(imageData);
}
break;

buildDockerImage(imageData).subscribe(event => {
console.log(event);
case 'subscribeToImageBuilder': {
imageBuilder.subscribe(event => {
conn.next({ type: 'imageBuildProgress', data: event });
});
}

break;

case 'startBuild':
@@ -21,26 +21,47 @@ <h1>Images</h1>
</div>
</div>

<div class="content-box">
<div class="columns is-multiline" *ngIf="!loading">
<div class="column is-12">
<div *ngIf="!loading">

<pre>{{ imageBuilds | json }}</pre>

<pre class="image-build-log" [innerHTML]="imageBuildLog | safeHtml"></pre>
<div class="content-box image-builder-box" *ngIf="!building && !success">
<h1>Build New Image</h1>
<div class="columns is-multiline">
<div class="column is-12">
<h2>Image name</h2>
<input type="text" class="image-name-input" [(ngModel)]="form.name" placeholder="Image Name">
<h2>Dockerfile</h2>
<app-editor [options]="editorOptions" [(ngModel)]="form.dockerfile" class="editor-large"></app-editor>
<h2>init.sh</h2>
<app-editor [options]="initEditorOptions" [(ngModel)]="form.initsh" class="editor-small"></app-editor>
<div class="images-buttons-container">
<button type="button" class="button w300 blue" (click)="buildImage()">Build Image</button>
</div>
</div>
</div>
</div>

<input type="text" class="image-name-input" [(ngModel)]="form.name">
<h2>Dockerfile</h2>
<app-editor [options]="editorOptions" [(ngModel)]="form.dockerfile" class="editor-large"></app-editor>
<h2>init.sh</h2>
<app-editor [options]="initEditorOptions" [(ngModel)]="form.initsh" class="editor-small"></app-editor>
<div class="images-buttons-container">
<button type="button" class="button w300 green" (click)="buildImage()">Build Image</button>
<div class="content-box image-builder-box" *ngIf="!building && success">
<h1>Image Build Done</h1>
<div class="columns is-multiline">
<div class="column is-12">
<div class="notification is-info">
<p class="has-text-centered">Image successfully built.</p>
</div>
</div>
</div>
</div>

<div class="content-box image-builder-box" *ngIf="building">
<p class="has-text-centered">
Building image <strong>{{ form.name }}</strong> <span *ngIf="imageBuildsText">({{ imageBuildsText }} layers)</span> ...
</p>
<pre class="image-build-log" [innerHTML]="imageBuildLog | safeHtml" *ngIf="imageBuildLog"></pre>
</div>

</div>



</div>
</div>
</div>
@@ -1,4 +1,5 @@
import { Component, OnInit, OnDestroy, NgZone } from '@angular/core';
import { Component, OnInit, OnDestroy, NgZone, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { SocketService } from '../../services/socket.service';
import * as ansiUp from 'ansi_up';

@@ -23,17 +24,24 @@ export class AppImagesComponent implements OnInit, OnDestroy {
initEditorOptions: any;
form: IImage;
imageBuilds: ImageBuildType[];
imageBuildsText: string;
imageBuildLog: string;
au: any;

constructor(private socketService: SocketService, private zone: NgZone) {
building: boolean;
success: boolean;

constructor(
private socketService: SocketService,
private zone: NgZone,
@Inject(DOCUMENT) private document: any
) {
this.loading = true;
this.imageBuilds = [];
this.imageBuildLog = '';

this.editorOptions = {
lineNumbers: false,
theme: 'vs',
lineNumbers: true,
theme: 'vs-dark',
language: 'dockerfile',
minimap: {
enabled: false
@@ -57,7 +65,7 @@ export class AppImagesComponent implements OnInit, OnDestroy {
this.initEditorOptions = Object.assign({}, this.editorOptions, { language: 'plaintext' });

this.form = {
name: 'unnamed_image',
name: 'abstruse',
dockerfile: [
'FROM ubuntu:17.04',
'',
@@ -123,6 +131,7 @@ export class AppImagesComponent implements OnInit, OnDestroy {
};

this.au = new ansiUp.default();
this.building = false;
}

ngOnInit() {
@@ -138,19 +147,38 @@ export class AppImagesComponent implements OnInit, OnDestroy {
output = null;
}

if (output) {
this.building = true;
}

if (output && output.id && output.progressDetail) {
const buildIndex = this.findImageBuild(event.data.name);
const layerIndex = this.findImageLayer(buildIndex, output.id);

this.zone.run(() => {
this.imageBuilds[buildIndex].layers[layerIndex] = output;
const length = this.imageBuilds[buildIndex].layers.length;
const done = this.imageBuilds[buildIndex].layers.filter(l => {
return l.status === 'Download complete' || l.status === 'Pull complete';
}).length;

this.imageBuildsText = done + '/' + length;
});
} else if (output && output.stream) {
this.imageBuildLog += this.au.ansi_to_html(output.stream);
if (output.stream.startsWith('Successfully built') || output.stream.startsWith('Successfully tagged')) {
this.building = false;
this.success = true;
} else {
this.imageBuildLog += this.au.ansi_to_html(output.stream);
this.scrollToBottom();
}
} else if (output && output.errorDetail) {
this.imageBuildLog += `<span style="color:rgb(255,85,85);">${output.errorDetail.message}</span>`;
this.scrollToBottom();
}
});

this.socketService.emit({ type: 'subscribeToImageBuilder' });
}

findImageBuild(imageName: string): number {
@@ -185,11 +213,17 @@ export class AppImagesComponent implements OnInit, OnDestroy {
}
}

scrollToBottom() {
const body = this.document.documentElement.scrollHeight;
window.scrollTo(0, body.scrollHeight);
}

ngOnDestroy() {

}

buildImage(): void {
this.building = true;
this.socketService.emit({ type: 'buildImage', data: this.form });
}
}
@@ -119,10 +119,12 @@
&.blue
background-image: linear-gradient(-1deg, #1991EB 2%, #2DA1F8 98%)
border-color: #1991EB
color: $white

&.yellow
background: linear-gradient(to bottom, #F7981C, #F76B1C)
border-color: #F7981C
color: $white

&:hover, &.is-hovered
background-image: linear-gradient(0deg, #EAEEF3 0%, #FFFFFF 100%)
@@ -9,7 +9,7 @@
height: 150px

.editor-wrapper
background: $white
background: #212121
border: 1px solid $border
padding: 10px
display: inline-block
@@ -1,15 +1,14 @@
.image-name-input
font-size: 22px
padding: 10px
font-size: 16px
padding: 10px 15px
display: inline-block
margin-bottom: 10px
border: 1px solid transparent
border: 1px solid $border
outline: none
width: 100%
color: $color

&:focus, &:active
border: 1px solid $border
color: #CECFCF
margin: 0 0 10px 0
background: #212121

.images-buttons-container
margin: 10px 0 0 0
@@ -25,3 +24,14 @@
font-family: $font-family-roboto-mono
color: #f8f8f2
font-size: 12px
margin: 30px 0 0 0

.image-builder-box

h1
margin-bottom: 20px
font-weight: $weight-semibold

h2
color: $black
font-size: 15px

0 comments on commit 5934043

Please sign in to comment.