Merge pull request #13 from xetorthio/session_setup
Add session templates
This commit is contained in:
1
api.go
1
api.go
@@ -61,6 +61,7 @@ func main() {
|
||||
r.HandleFunc("/ping", handlers.Ping).Methods("GET")
|
||||
corsRouter.HandleFunc("/instances/images", handlers.GetInstanceImages).Methods("GET")
|
||||
corsRouter.HandleFunc("/sessions/{sessionId}", handlers.GetSession).Methods("GET")
|
||||
corsRouter.HandleFunc("/sessions/{sessionId}/setup", handlers.SessionSetup).Methods("POST")
|
||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances", handlers.NewInstance).Methods("POST")
|
||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/uploads", handlers.FileUpload).Methods("POST")
|
||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", handlers.DeleteInstance).Methods("DELETE")
|
||||
|
||||
@@ -4,22 +4,27 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,6 +49,14 @@ type DockerApi interface {
|
||||
DisconnectNetwork(containerId, networkId string) error
|
||||
DeleteNetwork(id string) error
|
||||
Exec(instanceName string, command []string) (int, error)
|
||||
New(ip string, cert, key []byte) (DockerApi, error)
|
||||
SwarmInit() (*SwarmTokens, error)
|
||||
SwarmJoin(addr, token string) error
|
||||
}
|
||||
|
||||
type SwarmTokens struct {
|
||||
Manager string
|
||||
Worker string
|
||||
}
|
||||
|
||||
type docker struct {
|
||||
@@ -396,6 +409,73 @@ func (d *docker) DeleteNetwork(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *docker) New(ip string, cert, key []byte) (DockerApi, error) {
|
||||
// We check if the client needs to use TLS
|
||||
var tlsConfig *tls.Config
|
||||
if len(cert) > 0 && len(key) > 0 {
|
||||
tlsConfig = tlsconfig.ClientDefault()
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsCert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 1 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext}
|
||||
if tlsConfig != nil {
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
}
|
||||
cli := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
c, err := client.NewClient(fmt.Sprintf("http://%s:2375", ip), api.DefaultVersion, cli, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not connect to DinD docker daemon. %s", err)
|
||||
}
|
||||
// try to connect up to 5 times and then give up
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err := c.Ping(context.Background())
|
||||
if err != nil {
|
||||
if client.IsErrConnectionFailed(err) {
|
||||
// connection has failed, maybe instance is not ready yet, sleep and retry
|
||||
log.Printf("Connection to [%s] has failed, maybe instance is not ready yet, sleeping and retrying in 1 second. Try #%d\n", fmt.Sprintf("http://%s:2375", ip), i+1)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewDocker(c), nil
|
||||
}
|
||||
|
||||
func (d *docker) SwarmInit() (*SwarmTokens, error) {
|
||||
req := swarm.InitRequest{AdvertiseAddr: "eth0", ListenAddr: "0.0.0.0:2377"}
|
||||
_, err := d.c.SwarmInit(context.Background(), req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
swarmInfo, err := d.c.SwarmInspect(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SwarmTokens{
|
||||
Worker: swarmInfo.JoinTokens.Worker,
|
||||
Manager: swarmInfo.JoinTokens.Manager,
|
||||
}, nil
|
||||
}
|
||||
func (d *docker) SwarmJoin(addr, token string) error {
|
||||
req := swarm.JoinRequest{RemoteAddrs: []string{addr}, JoinToken: token, ListenAddr: "0.0.0.0:2377", AdvertiseAddr: "eth0"}
|
||||
return d.c.SwarmJoin(context.Background(), req)
|
||||
}
|
||||
|
||||
func NewDocker(c *client.Client) *docker {
|
||||
return &docker{c: c}
|
||||
}
|
||||
|
||||
35
handlers/session_setup.go
Normal file
35
handlers/session_setup.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/play-with-docker/play-with-docker/pwd"
|
||||
)
|
||||
|
||||
func SessionSetup(rw http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
sessionId := vars["sessionId"]
|
||||
|
||||
body := pwd.SessionSetupConf{}
|
||||
|
||||
json.NewDecoder(req.Body).Decode(&body)
|
||||
|
||||
s := core.SessionGet(sessionId)
|
||||
|
||||
if len(s.Instances) > 0 {
|
||||
log.Println("Cannot setup a session that contains instances")
|
||||
rw.WriteHeader(http.StatusConflict)
|
||||
rw.Write([]byte("Cannot setup a session that contains instances"))
|
||||
return
|
||||
}
|
||||
|
||||
err := core.SessionSetup(s, body)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ type mockDocker struct {
|
||||
connectNetwork func(container, network, ip string) (string, error)
|
||||
containerResize func(string, uint, uint) error
|
||||
createContainer func(opts docker.CreateContainerOpts) (string, error)
|
||||
execAttach func(instanceName string, command []string, out io.Writer) (int, error)
|
||||
new func(ip string, cert, key []byte) (docker.DockerApi, error)
|
||||
swarmInit func() (*docker.SwarmTokens, error)
|
||||
swarmJoin func(addr, token string) error
|
||||
}
|
||||
|
||||
func (m *mockDocker) CreateNetwork(id string) error {
|
||||
@@ -64,6 +68,9 @@ func (m *mockDocker) CreateContainer(opts docker.CreateContainerOpts) (string, e
|
||||
return "10.0.0.1", nil
|
||||
}
|
||||
func (m *mockDocker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
|
||||
if m.execAttach != nil {
|
||||
return m.execAttach(instanceName, command, out)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockDocker) DisconnectNetwork(containerId, networkId string) error {
|
||||
@@ -75,6 +82,24 @@ func (m *mockDocker) DeleteNetwork(id string) error {
|
||||
func (m *mockDocker) Exec(instanceName string, command []string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockDocker) New(ip string, cert, key []byte) (docker.DockerApi, error) {
|
||||
if m.new != nil {
|
||||
return m.new(ip, cert, key)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockDocker) SwarmInit() (*docker.SwarmTokens, error) {
|
||||
if m.swarmInit != nil {
|
||||
return m.swarmInit()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockDocker) SwarmJoin(addr, token string) error {
|
||||
if m.swarmJoin != nil {
|
||||
return m.swarmJoin(addr, token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockConn struct {
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ type PWDApi interface {
|
||||
SessionDeployStack(session *Session) error
|
||||
SessionGet(id string) *Session
|
||||
SessionLoadAndPrepare() error
|
||||
SessionSetup(session *Session, conf SessionSetupConf) error
|
||||
|
||||
InstanceNew(session *Session, conf InstanceConfig) (*Instance, error)
|
||||
InstanceResizeTerminal(instance *Instance, cols, rows uint) error
|
||||
|
||||
100
pwd/session.go
100
pwd/session.go
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/play-with-docker/play-with-docker/config"
|
||||
"github.com/play-with-docker/play-with-docker/docker"
|
||||
"github.com/twinj/uuid"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,17 @@ func (s *sessionBuilderWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type SessionSetupConf struct {
|
||||
Instances []SessionSetupInstanceConf `json:"instances"`
|
||||
}
|
||||
|
||||
type SessionSetupInstanceConf struct {
|
||||
Image string `json:"image"`
|
||||
Hostname string `json:"hostname"`
|
||||
IsSwarmManager bool `json:"is_swarm_manager"`
|
||||
IsSwarmWorker bool `json:"is_swarm_worker"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
rw sync.Mutex
|
||||
Id string `json:"id"`
|
||||
@@ -209,6 +221,94 @@ func (p *pwd) SessionLoadAndPrepare() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pwd) SessionSetup(session *Session, conf SessionSetupConf) error {
|
||||
var tokens *docker.SwarmTokens = nil
|
||||
var firstSwarmManager *Instance = nil
|
||||
|
||||
// first look for a swarm manager and create it
|
||||
for _, conf := range conf.Instances {
|
||||
if conf.IsSwarmManager {
|
||||
instanceConf := InstanceConfig{
|
||||
ImageName: conf.Image,
|
||||
Hostname: conf.Hostname,
|
||||
}
|
||||
i, err := p.InstanceNew(session, instanceConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.docker == nil {
|
||||
dock, err := p.docker.New(i.IP, i.Cert, i.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.docker = dock
|
||||
}
|
||||
tkns, err := i.docker.SwarmInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokens = tkns
|
||||
firstSwarmManager = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// now create the rest in parallel
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for _, c := range conf.Instances {
|
||||
if firstSwarmManager != nil && c.Hostname != firstSwarmManager.Hostname {
|
||||
wg.Add(1)
|
||||
go func(c SessionSetupInstanceConf) {
|
||||
defer wg.Done()
|
||||
instanceConf := InstanceConfig{
|
||||
ImageName: c.Image,
|
||||
Hostname: c.Hostname,
|
||||
}
|
||||
i, err := p.InstanceNew(session, instanceConf)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if c.IsSwarmManager || c.IsSwarmWorker {
|
||||
// check if we have connection to the daemon, if not, create it
|
||||
if i.docker == nil {
|
||||
dock, err := p.docker.New(i.IP, i.Cert, i.Key)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
i.docker = dock
|
||||
}
|
||||
}
|
||||
|
||||
if firstSwarmManager != nil {
|
||||
if c.IsSwarmManager {
|
||||
// this is a swarm manager
|
||||
// cluster has already been initiated, join as manager
|
||||
err := i.docker.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Manager)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if c.IsSwarmWorker {
|
||||
// this is a swarm worker
|
||||
err := i.docker.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Worker)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function should be called any time a session needs to be prepared:
|
||||
// 1. Like when it is created
|
||||
// 2. When it was loaded from storage
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package pwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/play-with-docker/play-with-docker/config"
|
||||
"github.com/play-with-docker/play-with-docker/docker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -80,3 +82,199 @@ func TestSessionNew(t *testing.T) {
|
||||
assert.Equal(t, expectedSessions, sessions)
|
||||
assert.True(t, saveCalled)
|
||||
}
|
||||
|
||||
func TestSessionSetup(t *testing.T) {
|
||||
swarmInitOnMaster1 := false
|
||||
manager2JoinedHasManager := false
|
||||
manager3JoinedHasManager := false
|
||||
worker1JoinedHasWorker := false
|
||||
|
||||
dock := &mockDocker{}
|
||||
dock.createContainer = func(opts docker.CreateContainerOpts) (string, error) {
|
||||
if opts.Hostname == "manager1" {
|
||||
return "10.0.0.1", nil
|
||||
} else if opts.Hostname == "manager2" {
|
||||
return "10.0.0.2", nil
|
||||
} else if opts.Hostname == "manager3" {
|
||||
return "10.0.0.3", nil
|
||||
} else if opts.Hostname == "worker1" {
|
||||
return "10.0.0.4", nil
|
||||
} else if opts.Hostname == "other" {
|
||||
return "10.0.0.5", nil
|
||||
} else {
|
||||
assert.Fail(t, "Should not have reached here")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
dock.new = func(ip string, cert, key []byte) (docker.DockerApi, error) {
|
||||
if ip == "10.0.0.1" {
|
||||
return &mockDocker{
|
||||
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||
swarmInitOnMaster1 = true
|
||||
return &docker.SwarmTokens{Worker: "worker-join-token", Manager: "manager-join-token"}, nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
if ip == "10.0.0.2" {
|
||||
return &mockDocker{
|
||||
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil, nil
|
||||
},
|
||||
swarmJoin: func(addr, token string) error {
|
||||
if addr == "10.0.0.1:2377" && token == "manager-join-token" {
|
||||
manager2JoinedHasManager = true
|
||||
return nil
|
||||
}
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
if ip == "10.0.0.3" {
|
||||
return &mockDocker{
|
||||
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil, nil
|
||||
},
|
||||
swarmJoin: func(addr, token string) error {
|
||||
if addr == "10.0.0.1:2377" && token == "manager-join-token" {
|
||||
manager3JoinedHasManager = true
|
||||
return nil
|
||||
}
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
if ip == "10.0.0.4" {
|
||||
return &mockDocker{
|
||||
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil, nil
|
||||
},
|
||||
swarmJoin: func(addr, token string) error {
|
||||
if addr == "10.0.0.1:2377" && token == "worker-join-token" {
|
||||
worker1JoinedHasWorker = true
|
||||
return nil
|
||||
}
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
assert.Fail(t, "Shouldn't have reached here.")
|
||||
return nil, nil
|
||||
}
|
||||
tasks := &mockTasks{}
|
||||
broadcast := &mockBroadcast{}
|
||||
storage := &mockStorage{}
|
||||
|
||||
p := NewPWD(dock, tasks, broadcast, storage)
|
||||
s, e := p.SessionNew(time.Hour, "", "")
|
||||
assert.Nil(t, e)
|
||||
|
||||
err := p.SessionSetup(s, SessionSetupConf{
|
||||
Instances: []SessionSetupInstanceConf{
|
||||
{
|
||||
Image: "franela/dind",
|
||||
IsSwarmManager: true,
|
||||
Hostname: "manager1",
|
||||
},
|
||||
{
|
||||
IsSwarmManager: true,
|
||||
Hostname: "manager2",
|
||||
},
|
||||
{
|
||||
Image: "franela/dind:overlay2-dev",
|
||||
IsSwarmManager: true,
|
||||
Hostname: "manager3",
|
||||
},
|
||||
{
|
||||
IsSwarmWorker: true,
|
||||
Hostname: "worker1",
|
||||
},
|
||||
{
|
||||
Hostname: "other",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 5, len(s.Instances))
|
||||
|
||||
manager1 := fmt.Sprintf("%s_manager1", s.Id[:8])
|
||||
manager1Received := *s.Instances[manager1]
|
||||
assert.Equal(t, Instance{
|
||||
Name: manager1,
|
||||
Image: "franela/dind",
|
||||
Hostname: "manager1",
|
||||
IP: "10.0.0.1",
|
||||
Alias: "",
|
||||
IsDockerHost: true,
|
||||
session: s,
|
||||
conn: manager1Received.conn,
|
||||
docker: manager1Received.docker,
|
||||
}, manager1Received)
|
||||
|
||||
manager2 := fmt.Sprintf("%s_manager2", s.Id[:8])
|
||||
manager2Received := *s.Instances[manager2]
|
||||
assert.Equal(t, Instance{
|
||||
Name: manager2,
|
||||
Image: "franela/dind",
|
||||
Hostname: "manager2",
|
||||
IP: "10.0.0.2",
|
||||
Alias: "",
|
||||
IsDockerHost: true,
|
||||
session: s,
|
||||
conn: manager2Received.conn,
|
||||
docker: manager2Received.docker,
|
||||
}, manager2Received)
|
||||
|
||||
manager3 := fmt.Sprintf("%s_manager3", s.Id[:8])
|
||||
manager3Received := *s.Instances[manager3]
|
||||
assert.Equal(t, Instance{
|
||||
Name: manager3,
|
||||
Image: "franela/dind:overlay2-dev",
|
||||
Hostname: "manager3",
|
||||
IP: "10.0.0.3",
|
||||
Alias: "",
|
||||
IsDockerHost: true,
|
||||
session: s,
|
||||
conn: manager3Received.conn,
|
||||
docker: manager3Received.docker,
|
||||
}, manager3Received)
|
||||
|
||||
worker1 := fmt.Sprintf("%s_worker1", s.Id[:8])
|
||||
worker1Received := *s.Instances[worker1]
|
||||
assert.Equal(t, Instance{
|
||||
Name: worker1,
|
||||
Image: "franela/dind",
|
||||
Hostname: "worker1",
|
||||
IP: "10.0.0.4",
|
||||
Alias: "",
|
||||
IsDockerHost: true,
|
||||
session: s,
|
||||
conn: worker1Received.conn,
|
||||
docker: worker1Received.docker,
|
||||
}, worker1Received)
|
||||
|
||||
other := fmt.Sprintf("%s_other", s.Id[:8])
|
||||
otherReceived := *s.Instances[other]
|
||||
assert.Equal(t, Instance{
|
||||
Name: other,
|
||||
Image: "franela/dind",
|
||||
Hostname: "other",
|
||||
IP: "10.0.0.5",
|
||||
Alias: "",
|
||||
IsDockerHost: true,
|
||||
session: s,
|
||||
conn: otherReceived.conn,
|
||||
docker: otherReceived.docker,
|
||||
}, otherReceived)
|
||||
|
||||
assert.True(t, swarmInitOnMaster1)
|
||||
assert.True(t, manager2JoinedHasManager)
|
||||
assert.True(t, manager3JoinedHasManager)
|
||||
assert.True(t, worker1JoinedHasWorker)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
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/', '');
|
||||
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', 'SessionService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService, SessionService) {
|
||||
$scope.sessionId = SessionService.getCurrentSessionId();
|
||||
$scope.instances = [];
|
||||
$scope.idx = {};
|
||||
$scope.selectedInstance = null;
|
||||
@@ -363,7 +363,7 @@
|
||||
$mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24);
|
||||
}])
|
||||
.component('settingsIcon', {
|
||||
template : "<md-button ng-click='$ctrl.onClick()'><md-icon class='material-icons'>settings</md-icon></md-button>",
|
||||
template : "<md-button class='md-mini' ng-click='$ctrl.onClick()'><md-icon class='material-icons'>settings</md-icon></md-button>",
|
||||
controller : function($mdDialog) {
|
||||
var $ctrl = this;
|
||||
$ctrl.onClick = function() {
|
||||
@@ -376,6 +376,42 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.component('templatesIcon', {
|
||||
template : "<md-button class='md-mini' ng-click='$ctrl.onClick()'><md-icon class='material-icons'>build</md-icon></md-button>",
|
||||
controller : function($mdDialog) {
|
||||
var $ctrl = this;
|
||||
$ctrl.onClick = function() {
|
||||
$mdDialog.show({
|
||||
controller : function() {},
|
||||
template : "<templates-dialog></templates-dialog>",
|
||||
parent: angular.element(document.body),
|
||||
clickOutsideToClose : true
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.component("templatesDialog", {
|
||||
templateUrl : "templates-modal.html",
|
||||
controller : function($mdDialog, $scope, SessionService) {
|
||||
var $ctrl = this;
|
||||
$scope.building = false;
|
||||
$scope.templates = SessionService.getAvailableTemplates();
|
||||
$ctrl.close = function() {
|
||||
$mdDialog.cancel();
|
||||
}
|
||||
$ctrl.setupSession = function(setup) {
|
||||
$scope.building = true;
|
||||
SessionService.setup(setup, function(err) {
|
||||
$scope.building = false;
|
||||
if (err) {
|
||||
$scope.errorMessage = err;
|
||||
return;
|
||||
}
|
||||
$ctrl.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.component("settingsDialog", {
|
||||
templateUrl : "settings-modal.html",
|
||||
controller : function($mdDialog, KeyboardShortcutService, $rootScope, InstanceService, TerminalService) {
|
||||
@@ -419,6 +455,58 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.service("SessionService", function($http) {
|
||||
var templates = [
|
||||
{
|
||||
title: '3 Managers and 2 Workers',
|
||||
icon: '/assets/swarm.png',
|
||||
setup: {
|
||||
instances: [
|
||||
{hostname: 'manager1', is_swarm_manager: true},
|
||||
{hostname: 'manager2', is_swarm_manager: true},
|
||||
{hostname: 'manager3', is_swarm_manager: true},
|
||||
{hostname: 'worker1', is_swarm_worker: true},
|
||||
{hostname: 'worker2', is_swarm_worker: true}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '5 Managers and no workers',
|
||||
icon: '/assets/swarm.png',
|
||||
setup: {
|
||||
instances: [
|
||||
{hostname: 'manager1', is_swarm_manager: true},
|
||||
{hostname: 'manager2', is_swarm_manager: true},
|
||||
{hostname: 'manager3', is_swarm_manager: true},
|
||||
{hostname: 'manager4', is_swarm_manager: true},
|
||||
{hostname: 'manager5', is_swarm_manager: true}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
getAvailableTemplates: getAvailableTemplates,
|
||||
getCurrentSessionId: getCurrentSessionId,
|
||||
setup: setup,
|
||||
};
|
||||
|
||||
function getCurrentSessionId() {
|
||||
return window.location.pathname.replace('/p/', '');
|
||||
}
|
||||
function getAvailableTemplates() {
|
||||
return templates;
|
||||
}
|
||||
function setup(plan, cb) {
|
||||
return $http
|
||||
.post("/sessions/" + getCurrentSessionId() + "/setup", plan)
|
||||
.then(function(response) {
|
||||
if (cb) cb();
|
||||
}, function(response) {
|
||||
if (cb) cb(response.data);
|
||||
});
|
||||
}
|
||||
})
|
||||
.service("InstanceService", function($http) {
|
||||
var instanceImages = [];
|
||||
_prepopulateAvailableImages();
|
||||
|
||||
@@ -59,3 +59,6 @@ md-input-container .md-errors-spacer {
|
||||
background-color: rgba(0,0,0,.5);
|
||||
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
|
||||
}
|
||||
.md-mini {
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
BIN
www/assets/swarm.png
Normal file
BIN
www/assets/swarm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -47,6 +47,7 @@
|
||||
<md-button class="md-warn md-raised" ng-click="closeSession()">Close session</md-button>
|
||||
<div class="md-toolbar-tools">
|
||||
<h1 class="md-toolbar-tools">Instances</h1>
|
||||
<templates-icon></templates-icon>
|
||||
<settings-icon></settings-icon>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
@@ -145,6 +146,49 @@
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/ng-template" id="templates-modal.html">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Templates</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="$ctrl.close()">
|
||||
<md-icon class="material-icon" aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content" style="width:600px;">
|
||||
<div layout="row" layout-sm="column" layout-align="space-around" ng-if="building">
|
||||
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
|
||||
</div>
|
||||
<div layout="row" ng-if="errorMessage">
|
||||
<div flex="100" style="margin-top: 20px; text-align:center; font-weight: bold; color: red;">
|
||||
{{errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
<md-list flex ng-if="!building">
|
||||
<md-list-item class="md-3-line" ng-repeat="template in templates" ng-click="$ctrl.setupSession(template.setup)">
|
||||
<md-card md-theme="default" md-theme-watch>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">{{template.title}}</span>
|
||||
</md-card-title-text>
|
||||
<md-card-title-media>
|
||||
<div class="md-media-sm card-media"><img ng-src="{{template.icon}}" style="height: 75px;" class="md-card-image"></div>
|
||||
</md-card-title-media>
|
||||
</md-card-title>
|
||||
</md-card>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="$ctrl.close()">
|
||||
Close
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</script>
|
||||
<script type="text/ng-template" id="settings-modal.html">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
|
||||
Reference in New Issue
Block a user