UI modifications for stack creation
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/play-with-docker/play-with-docker/services"
|
"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)
|
vars := mux.Vars(r)
|
||||||
sessionId := vars["sessionId"]
|
sessionId := vars["sessionId"]
|
||||||
|
|
||||||
stack := r.URL.Query().Get("stack")
|
|
||||||
s := services.GetSession(sessionId)
|
s := services.GetSession(sessionId)
|
||||||
if stack != "" {
|
if s.Stack != "" {
|
||||||
go deployStack(s, stack)
|
go s.DeployStack()
|
||||||
}
|
}
|
||||||
http.ServeFile(w, r, "./www/index.html")
|
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)
|
duration := services.GetDuration(reqDur)
|
||||||
s, err := services.NewSession(duration)
|
s, err := services.NewSession(duration, stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
//TODO: Return some error code
|
//TODO: Return some error code
|
||||||
@@ -54,7 +54,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if stack != "" {
|
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
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s", hostname, s.Id), http.StatusFound)
|
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
|
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"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -56,6 +57,17 @@ type Session struct {
|
|||||||
scheduled bool `json:"-"`
|
scheduled bool `json:"-"`
|
||||||
ticker *time.Ticker `json:"-"`
|
ticker *time.Ticker `json:"-"`
|
||||||
PwdIpAddress string `json:"pwd_ip_address"`
|
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() {
|
func (s *Session) Lock() {
|
||||||
@@ -78,6 +90,48 @@ func (s *Session) GetSmallestViewPort() ViewPort {
|
|||||||
return ViewPort{Rows: minRows, Cols: minCols}
|
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) {
|
func (s *Session) AddNewClient(c *Client) {
|
||||||
s.clients = append(s.clients, c)
|
s.clients = append(s.clients, c)
|
||||||
setGauges()
|
setGauges()
|
||||||
@@ -243,12 +297,16 @@ func GetDuration(reqDur string) time.Duration {
|
|||||||
return defaultDuration
|
return defaultDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(duration time.Duration) (*Session, error) {
|
func NewSession(duration time.Duration, stack string) (*Session, error) {
|
||||||
s := &Session{}
|
s := &Session{}
|
||||||
s.Id = uuid.NewV4().String()
|
s.Id = uuid.NewV4().String()
|
||||||
s.Instances = map[string]*Instance{}
|
s.Instances = map[string]*Instance{}
|
||||||
s.CreatedAt = time.Now()
|
s.CreatedAt = time.Now()
|
||||||
s.ExpiresAt = s.CreatedAt.Add(duration)
|
s.ExpiresAt = s.CreatedAt.Add(duration)
|
||||||
|
if stack == "" {
|
||||||
|
s.Ready = true
|
||||||
|
}
|
||||||
|
s.Stack = stack
|
||||||
log.Printf("NewSession id=[%s]\n", s.Id)
|
log.Printf("NewSession id=[%s]\n", s.Id)
|
||||||
|
|
||||||
// Schedule cleanup of the session
|
// Schedule cleanup of the session
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
}, 500);
|
}, 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) {
|
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.sessionId = window.location.pathname.replace('/p/', '');
|
||||||
$scope.instances = [];
|
$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) {
|
$scope.getSession = function(sessionId) {
|
||||||
$http({
|
$http({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/sessions/' + $scope.sessionId,
|
url: '/sessions/' + $scope.sessionId,
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
|
$scope.setSessionState(response.data.ready);
|
||||||
|
|
||||||
if (response.data.created_at) {
|
if (response.data.created_at) {
|
||||||
$scope.expiresAt = moment(response.data.expires_at);
|
$scope.expiresAt = moment(response.data.expires_at);
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
@@ -104,6 +129,14 @@
|
|||||||
}
|
}
|
||||||
var socket = io({ path: '/sessions/' + sessionId + '/ws' });
|
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) {
|
socket.on('terminal out', function(name, data) {
|
||||||
var instance = $scope.idx[name];
|
var instance = $scope.idx[name];
|
||||||
|
|
||||||
@@ -236,6 +269,21 @@
|
|||||||
|
|
||||||
$scope.getSession($scope.sessionId);
|
$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) {
|
function createTerminal(instance, cb) {
|
||||||
if (instance.term) {
|
if (instance.term) {
|
||||||
return instance.term;
|
return instance.term;
|
||||||
|
|||||||
@@ -113,6 +113,39 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</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">
|
<script type="text/ng-template" id="settings-modal.html">
|
||||||
<md-toolbar>
|
<md-toolbar>
|
||||||
<div class="md-toolbar-tools">
|
<div class="md-toolbar-tools">
|
||||||
|
|||||||
Reference in New Issue
Block a user