UI modifications for stack creation
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [];
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user