UI modifications for stack creation

This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-05-16 17:37:34 -03:00
parent bfa90865a1
commit 1634200ef7
6 changed files with 169 additions and 28 deletions

View File

@@ -1,9 +1,7 @@
package handlers
import (
"log"
"net/http"
"path"
"github.com/gorilla/mux"
"github.com/play-with-docker/play-with-docker/services"
@@ -13,29 +11,9 @@ func Home(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionId := vars["sessionId"]
stack := r.URL.Query().Get("stack")
s := services.GetSession(sessionId)
if stack != "" {
go deployStack(s, stack)
if s.Stack != "" {
go s.DeployStack()
}
http.ServeFile(w, r, "./www/index.html")
}
func deployStack(s *services.Session, stack string) {
i, err := services.NewInstance(s, services.InstanceConfig{})
if err != nil {
log.Printf("Error creating instance for stack [%s]: %s\n", stack, err)
}
err = i.UploadFromURL("https://raw.githubusercontent.com/play-with-docker/stacks/master" + stack)
if err != nil {
log.Printf("Error uploading stack file [%s]: %s\n", stack, err)
}
fileName := path.Base(stack)
code, err := services.Exec(i.Name, []string{"docker-compose", "-f", "/var/run/pwd/uploads/" + fileName, "up", "-d"})
if err != nil {
log.Printf("Error executing stack [%s]: %s\n", stack, err)
}
log.Printf("Stack execution finished with code %d\n", code)
}

View File

@@ -39,7 +39,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
}
duration := services.GetDuration(reqDur)
s, err := services.NewSession(duration)
s, err := services.NewSession(duration, stack)
if err != nil {
log.Println(err)
//TODO: Return some error code
@@ -54,7 +54,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
}
if stack != "" {
http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s?stack=%s", hostname, s.Id, stack), http.StatusFound)
http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s", hostname, s.Id), http.StatusFound)
return
}
http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s", hostname, s.Id), http.StatusFound)

View File

@@ -365,3 +365,27 @@ func Exec(instanceName string, command []string) (int, error) {
return ins.ExitCode, nil
}
func ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
e, err := c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: true})
if err != nil {
return 0, err
}
resp, err := c.ContainerExecAttach(context.Background(), e.ID, types.ExecConfig{AttachStdout: true, AttachStderr: true, Tty: true})
if err != nil {
return 0, err
}
io.Copy(out, resp.Reader)
var ins types.ContainerExecInspect
for _ = range time.Tick(1 * time.Second) {
ins, err = c.ContainerExecInspect(context.Background(), e.ID)
if ins.Running {
continue
}
if err != nil {
return 0, err
}
break
}
return ins.ExitCode, nil
}

View File

@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"os"
"path"
"sort"
"strings"
"sync"
@@ -56,6 +57,17 @@ type Session struct {
scheduled bool `json:"-"`
ticker *time.Ticker `json:"-"`
PwdIpAddress string `json:"pwd_ip_address"`
Ready bool `json:"ready"`
Stack string `json:"stack"`
}
type sessionBuilderWriter struct {
session *Session
}
func (s *sessionBuilderWriter) Write(p []byte) (n int, err error) {
wsServer.BroadcastTo(s.session.Id, "session builder out", string(p))
return len(p), nil
}
func (s *Session) Lock() {
@@ -78,6 +90,48 @@ func (s *Session) GetSmallestViewPort() ViewPort {
return ViewPort{Rows: minRows, Cols: minCols}
}
func (s *Session) DeployStack() error {
s.Lock()
defer s.Unlock()
if s.Ready {
// a stack was already deployed on this session, just ignore
return nil
}
s.setReady(false)
i, err := NewInstance(s, InstanceConfig{})
if err != nil {
log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, err)
return err
}
err = i.UploadFromURL("https://raw.githubusercontent.com/play-with-docker/stacks/master" + s.Stack)
if err != nil {
log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err)
return err
}
w := sessionBuilderWriter{session: s}
fileName := path.Base(s.Stack)
code, err := ExecAttach(i.Name, []string{"docker-compose", "-f", "/var/run/pwd/uploads/" + fileName, "up", "-d"}, &w)
if err != nil {
log.Printf("Error executing stack [%s]: %s\n", s.Stack, err)
return err
}
log.Printf("Stack execution finished with code %d\n", code)
s.setReady(true)
if err := saveSessionsToDisk(); err != nil {
return err
}
return nil
}
func (s *Session) setReady(ready bool) {
s.Ready = ready
wsServer.BroadcastTo(s.Id, "session ready", s.Ready)
}
func (s *Session) AddNewClient(c *Client) {
s.clients = append(s.clients, c)
setGauges()
@@ -243,12 +297,16 @@ func GetDuration(reqDur string) time.Duration {
return defaultDuration
}
func NewSession(duration time.Duration) (*Session, error) {
func NewSession(duration time.Duration, stack string) (*Session, error) {
s := &Session{}
s.Id = uuid.NewV4().String()
s.Instances = map[string]*Instance{}
s.CreatedAt = time.Now()
s.ExpiresAt = s.CreatedAt.Add(duration)
if stack == "" {
s.Ready = true
}
s.Stack = stack
log.Printf("NewSession id=[%s]\n", s.Id)
// Schedule cleanup of the session

View File

@@ -11,6 +11,14 @@
}, 500);
}]);
function SessionBuilderModalController($mdDialog, $scope) {
$scope.createBuilderTerminal();
$scope.closeSessionBuilder = function() {
$mdDialog.cancel();
}
}
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService) {
$scope.sessionId = window.location.pathname.replace('/p/', '');
$scope.instances = [];
@@ -23,7 +31,7 @@
$scope.newInstanceBtnText = '+ Add new instance';
$scope.deleteInstanceBtnText = 'Delete';
$scope.isInstanceBeingDeleted = false;
var selectedKeyboardShortcuts = KeyboardShortcutService.getCurrentShortcuts();
angular.element($window).bind('resize', function() {
@@ -90,11 +98,28 @@
});
}
$scope.setSessionState = function(state) {
$scope.ready = state;
if (!state) {
$mdDialog.show({
controller: SessionBuilderModalController,
templateUrl: "session-builder-modal.html",
parent: angular.element(document.body),
clickOutsideToClose: false,
scope: $scope,
preserveScope: true
});
}
}
$scope.getSession = function(sessionId) {
$http({
method: 'GET',
url: '/sessions/' + $scope.sessionId,
}).then(function(response) {
$scope.setSessionState(response.data.ready);
if (response.data.created_at) {
$scope.expiresAt = moment(response.data.expires_at);
setInterval(function() {
@@ -104,6 +129,14 @@
}
var socket = io({ path: '/sessions/' + sessionId + '/ws' });
socket.on('session ready', function(ready) {
$scope.setSessionState(ready);
});
socket.on('session builder out', function(data) {
$scope.builderTerminal.write(data);
});
socket.on('terminal out', function(name, data) {
var instance = $scope.idx[name];
@@ -236,6 +269,21 @@
$scope.getSession($scope.sessionId);
$scope.createBuilderTerminal = function() {
var builderTerminalContainer = document.getElementById('builder-terminal');
// For some reason the dialog DOM might not be ready, so we just keep trying
if (!builderTerminalContainer) {
setTimeout($scope.createBuilderTerminal, 100);
return;
}
var term = new Terminal({
cursorBlink: false
});
term.open(builderTerminalContainer);
term.resize(80, 24);
$scope.builderTerminal = term;
}
function createTerminal(instance, cb) {
if (instance.term) {
return instance.term;

View File

@@ -113,6 +113,39 @@
</section>
</div>
<script type="text/ng-template" id="session-builder-modal.html">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>Session stack builder</h2>
<span flex></span>
</div>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content" style="width:800px;">
<div layout="row">
<div flex="100" style="margin-bottom: 20px;">
We are building your stack. This might take a while.<br/>
</div>
</div>
<div layout="row" layout-align="center center">
<div id="builder-terminal" flex="100" style="height: 450px;">
</div>
</div>
<div layout="row" ng-if="ready">
<div flex="100" style="margin-top: 20px;">
Your session is ready!
</div>
</div>
</div>
<md-dialog-actions layout="row" ng-if="ready">
<span flex></span>
<md-button ng-click="closeSessionBuilder()">
Close
</md-button>
</md-dialog-actions>
</md-dialog-content>
</script>
<script type="text/ng-template" id="settings-modal.html">
<md-toolbar>
<div class="md-toolbar-tools">