Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54750f002a | |||
| 4c2dfb3346 | |||
| 8ae3abf29e | |||
| 362f036a68 | |||
| 0d0072a50e | |||
| 173ea372c2 | |||
| 8c75f705e2 | |||
| b1863430df | |||
| c51db23c32 | |||
| c40f120da2 | |||
| a7cb0ca823 | |||
| 7817d4bd0b | |||
| edadce359c | |||
| e1bf9599ef | |||
| c3ba9e6a53 | |||
| 10174b98b9 | |||
| 6acfb580dc | |||
| 340ec841fe | |||
| a515b96a46 | |||
| 46da85c8cf | |||
| f52ac8fb12 | |||
| 0e28aebd65 | |||
| 35892525ff | |||
| d2f3309842 | |||
| 03f6cc0acf | |||
| f8c7ee7ae6 | |||
| 00daedca30 | |||
| e2b8633aac | |||
| 50dbb572b1 | |||
| 95b595d2a9 | |||
| f57ce8b327 | |||
| 5787df5599 | |||
| 52ac9504c1 | |||
| 1da64f2e75 | |||
| 8bf3f669d0 | |||
| eec10541b3 | |||
| e0b09f20b0 | |||
| 8e40eb1844 | |||
| c9e060d574 | |||
| 9c9e16b2b2 | |||
| 35f7ce5f3d | |||
| 45e7938c5c | |||
| fbd9139928 | |||
| d0da9860af | |||
| 46d8dba137 | |||
| 3660f6eeb5 | |||
| 39236ae84e | |||
| 7dcf5c2d0b | |||
| d0e147137d | |||
| bdb23a8dd2 | |||
| 7922ecc4a1 | |||
| 728ef35cc1 | |||
| f3a23c7dd1 | |||
| 283faca4f7 | |||
| 2b2850d17a | |||
| 997af882c4 | |||
| 75b3a78e2b | |||
| d8f6b14726 | |||
| 406757d751 | |||
| f3b5f803f5 | |||
| f1d9b72a06 | |||
| 9513da80f6 | |||
| ca036b56c1 | |||
| 27a388a030 | |||
| 65cde27334 | |||
| 2275467bdc | |||
| 688b15fb4b | |||
| 3362ba0c8c | |||
| 39cf4d75ff | |||
| 13d8d38bf9 | |||
| e51246ee78 | |||
| 4ab580923f | |||
| 547511c8aa | |||
| 8a101f67f6 | |||
| 3ee2e20f8e | |||
| 6b9f3dad7a | |||
| a2d41e5316 | |||
| 3548f0db6f | |||
| 521cc3d6ab |
@@ -26,6 +26,14 @@ You can try out the public demo instance: http://demo.portainer.io/ (login with
|
||||
|
||||
Please note that the public demo cluster is **reset every 15min**.
|
||||
|
||||
Alternatively, you can deploy a copy of the demo stack inside a [play-with-docker (PWD)](https://labs.play-with-docker.com) playground:
|
||||
|
||||
- Browse [PWD/?stack=portainer-demo/play-with-docker/docker-stack.yml](http://play-with-docker.com/?stack=https://raw.githubusercontent.com/portainer/portainer-demo/master/play-with-docker/docker-stack.yml)
|
||||
- Sign in with your [Docker ID](https://docs.docker.com/docker-id)
|
||||
- Follow [these](https://github.com/portainer/portainer-demo/blob/master/play-with-docker/docker-stack.yml#L5-L8) steps.
|
||||
|
||||
Unlike the public demo, the playground sessions are deleted after 4 hours. Apart from that, all the settings are same, including default credentials.
|
||||
|
||||
## Getting started
|
||||
|
||||
* [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package bolt
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion7() error {
|
||||
legacySettings, err := m.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacySettings.DisplayDonationHeader = true
|
||||
|
||||
err = m.SettingsService.StoreSettings(legacySettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -81,6 +81,14 @@ func (m *Migrator) Migrate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1449
|
||||
if m.CurrentDBVersion < 7 {
|
||||
err := m.updateSettingsToVersion7()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := m.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+3
-3
@@ -31,12 +31,11 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
kingpin.Version(version)
|
||||
|
||||
flags := &portainer.CLIFlags{
|
||||
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(),
|
||||
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints").String(),
|
||||
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source").Default(defaultSyncInterval).String(),
|
||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(),
|
||||
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints").String(),
|
||||
NoAuth: kingpin.Flag("no-auth", "Disable authentication").Default(defaultNoAuth).Bool(),
|
||||
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
|
||||
TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(),
|
||||
@@ -46,6 +45,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL").Default(defaultSSL).Bool(),
|
||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").Default(defaultSSLCertPath).String(),
|
||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").Default(defaultSSLKeyPath).String(),
|
||||
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source").Default(defaultSyncInterval).String(),
|
||||
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||
// Deprecated flags
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/portainer/portainer/cron"
|
||||
"github.com/portainer/portainer/crypto"
|
||||
"github.com/portainer/portainer/exec"
|
||||
"github.com/portainer/portainer/file"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
"github.com/portainer/portainer/git"
|
||||
"github.com/portainer/portainer/http"
|
||||
"github.com/portainer/portainer/jwt"
|
||||
@@ -31,7 +31,7 @@ func initCLI() *portainer.CLIFlags {
|
||||
}
|
||||
|
||||
func initFileService(dataStorePath string) portainer.FileService {
|
||||
fileService, err := file.NewService(dataStorePath, "")
|
||||
fileService, err := filesystem.NewService(dataStorePath, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -127,6 +127,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
||||
if err == portainer.ErrSettingsNotFound {
|
||||
settings := &portainer.Settings{
|
||||
LogoURL: *flags.Logo,
|
||||
DisplayDonationHeader: true,
|
||||
DisplayExternalContributors: false,
|
||||
AuthenticationMethod: portainer.AuthenticationInternal,
|
||||
LDAPSettings: portainer.LDAPSettings{
|
||||
|
||||
@@ -54,10 +54,15 @@ func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error {
|
||||
}
|
||||
|
||||
// Deploy executes the docker stack deploy command.
|
||||
func (manager *StackManager) Deploy(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
||||
func (manager *StackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
|
||||
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
|
||||
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
|
||||
args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
||||
|
||||
if prune {
|
||||
args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
||||
} else {
|
||||
args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
||||
}
|
||||
|
||||
env := make([]string, 0)
|
||||
for _, envvar := range stack.Env {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package file
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -48,7 +48,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
|
||||
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoints"):
|
||||
if strings.Contains(r.URL.Path, "/docker") {
|
||||
if strings.Contains(r.URL.Path, "/docker/") {
|
||||
http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r)
|
||||
} else if strings.Contains(r.URL.Path, "/stacks") {
|
||||
http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/file"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
@@ -46,6 +46,7 @@ func NewSettingsHandler(bouncer *security.RequestBouncer) *SettingsHandler {
|
||||
type (
|
||||
publicSettingsResponse struct {
|
||||
LogoURL string `json:"LogoURL"`
|
||||
DisplayDonationHeader bool `json:"DisplayDonationHeader"`
|
||||
DisplayExternalContributors bool `json:"DisplayExternalContributors"`
|
||||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
@@ -56,6 +57,7 @@ type (
|
||||
TemplatesURL string `valid:"required"`
|
||||
LogoURL string `valid:""`
|
||||
BlackListedLabels []portainer.Pair `valid:""`
|
||||
DisplayDonationHeader bool `valid:""`
|
||||
DisplayExternalContributors bool `valid:""`
|
||||
AuthenticationMethod int `valid:"required"`
|
||||
LDAPSettings portainer.LDAPSettings `valid:""`
|
||||
@@ -90,6 +92,7 @@ func (handler *SettingsHandler) handleGetPublicSettings(w http.ResponseWriter, r
|
||||
|
||||
publicSettings := &publicSettingsResponse{
|
||||
LogoURL: settings.LogoURL,
|
||||
DisplayDonationHeader: settings.DisplayDonationHeader,
|
||||
DisplayExternalContributors: settings.DisplayExternalContributors,
|
||||
AuthenticationMethod: settings.AuthenticationMethod,
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
@@ -118,6 +121,7 @@ func (handler *SettingsHandler) handlePutSettings(w http.ResponseWriter, r *http
|
||||
TemplatesURL: req.TemplatesURL,
|
||||
LogoURL: req.LogoURL,
|
||||
BlackListedLabels: req.BlackListedLabels,
|
||||
DisplayDonationHeader: req.DisplayDonationHeader,
|
||||
DisplayExternalContributors: req.DisplayExternalContributors,
|
||||
LDAPSettings: req.LDAPSettings,
|
||||
AllowBindMountsForRegularUsers: req.AllowBindMountsForRegularUsers,
|
||||
@@ -134,11 +138,11 @@ func (handler *SettingsHandler) handlePutSettings(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
if (settings.LDAPSettings.TLSConfig.TLS || settings.LDAPSettings.StartTLS) && !settings.LDAPSettings.TLSConfig.TLSSkipVerify {
|
||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(file.LDAPStorePath, portainer.TLSFileCA)
|
||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
|
||||
settings.LDAPSettings.TLSConfig.TLSCACertPath = caCertPath
|
||||
} else {
|
||||
settings.LDAPSettings.TLSConfig.TLSCACertPath = ""
|
||||
err := handler.FileService.DeleteTLSFiles(file.LDAPStorePath)
|
||||
err := handler.FileService.DeleteTLSFiles(filesystem.LDAPStorePath)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
}
|
||||
@@ -165,7 +169,7 @@ func (handler *SettingsHandler) handlePutSettingsLDAPCheck(w http.ResponseWriter
|
||||
}
|
||||
|
||||
if (req.LDAPSettings.TLSConfig.TLS || req.LDAPSettings.StartTLS) && !req.LDAPSettings.TLSConfig.TLSSkipVerify {
|
||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(file.LDAPStorePath, portainer.TLSFileCA)
|
||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
|
||||
req.LDAPSettings.TLSConfig.TLSCACertPath = caCertPath
|
||||
}
|
||||
|
||||
|
||||
+49
-12
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/file"
|
||||
"github.com/portainer/portainer/filesystem"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
@@ -37,6 +37,14 @@ type StackHandler struct {
|
||||
StackManager portainer.StackManager
|
||||
}
|
||||
|
||||
type stackDeploymentConfig struct {
|
||||
endpoint *portainer.Endpoint
|
||||
stack *portainer.Stack
|
||||
prune bool
|
||||
dockerhub *portainer.DockerHub
|
||||
registries []portainer.Registry
|
||||
}
|
||||
|
||||
// NewStackHandler returns a new instance of StackHandler.
|
||||
func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
|
||||
h := &StackHandler{
|
||||
@@ -78,6 +86,7 @@ type (
|
||||
putStackRequest struct {
|
||||
StackFileContent string `valid:"required"`
|
||||
Env []portainer.Pair `valid:""`
|
||||
Prune bool `valid:"-"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -166,7 +175,7 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
|
||||
ID: portainer.StackID(stackName + "_" + swarmID),
|
||||
Name: stackName,
|
||||
SwarmID: swarmID,
|
||||
EntryPoint: file.ComposeFileDefaultName,
|
||||
EntryPoint: filesystem.ComposeFileDefaultName,
|
||||
Env: req.Env,
|
||||
}
|
||||
|
||||
@@ -207,7 +216,14 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
|
||||
config := stackDeploymentConfig{
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
dockerhub: dockerhub,
|
||||
registries: filteredRegistries,
|
||||
prune: false,
|
||||
}
|
||||
err = handler.deployStack(&config)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
@@ -264,7 +280,7 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
|
||||
}
|
||||
|
||||
if req.PathInRepository == "" {
|
||||
req.PathInRepository = file.ComposeFileDefaultName
|
||||
req.PathInRepository = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
|
||||
stacks, err := handler.StackService.Stacks()
|
||||
@@ -334,7 +350,14 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
|
||||
config := stackDeploymentConfig{
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
dockerhub: dockerhub,
|
||||
registries: filteredRegistries,
|
||||
prune: false,
|
||||
}
|
||||
err = handler.deployStack(&config)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
@@ -404,7 +427,7 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
|
||||
ID: portainer.StackID(stackName + "_" + swarmID),
|
||||
Name: stackName,
|
||||
SwarmID: swarmID,
|
||||
EntryPoint: file.ComposeFileDefaultName,
|
||||
EntryPoint: filesystem.ComposeFileDefaultName,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
@@ -445,7 +468,14 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
|
||||
config := stackDeploymentConfig{
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
dockerhub: dockerhub,
|
||||
registries: filteredRegistries,
|
||||
prune: false,
|
||||
}
|
||||
err = handler.deployStack(&config)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
@@ -637,7 +667,14 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
|
||||
config := stackDeploymentConfig{
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
dockerhub: dockerhub,
|
||||
registries: filteredRegistries,
|
||||
prune: req.Prune,
|
||||
}
|
||||
err = handler.deployStack(&config)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
@@ -732,22 +769,22 @@ func (handler *StackHandler) handleDeleteStack(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *StackHandler) deployStack(endpoint *portainer.Endpoint, stack *portainer.Stack, dockerhub *portainer.DockerHub, registries []portainer.Registry) error {
|
||||
func (handler *StackHandler) deployStack(config *stackDeploymentConfig) error {
|
||||
handler.stackCreationMutex.Lock()
|
||||
|
||||
err := handler.StackManager.Login(dockerhub, registries, endpoint)
|
||||
err := handler.StackManager.Login(config.dockerhub, config.registries, config.endpoint)
|
||||
if err != nil {
|
||||
handler.stackCreationMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.StackManager.Deploy(stack, endpoint)
|
||||
err = handler.StackManager.Deploy(config.stack, config.prune, config.endpoint)
|
||||
if err != nil {
|
||||
handler.stackCreationMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.StackManager.Logout(endpoint)
|
||||
err = handler.StackManager.Logout(config.endpoint)
|
||||
if err != nil {
|
||||
handler.stackCreationMutex.Unlock()
|
||||
return err
|
||||
|
||||
+7
-6
@@ -14,9 +14,8 @@ type (
|
||||
Addr *string
|
||||
Assets *string
|
||||
Data *string
|
||||
ExternalEndpoints *string
|
||||
SyncInterval *string
|
||||
Endpoint *string
|
||||
ExternalEndpoints *string
|
||||
NoAuth *bool
|
||||
NoAnalytics *bool
|
||||
TLSVerify *bool
|
||||
@@ -26,12 +25,13 @@ type (
|
||||
SSL *bool
|
||||
SSLCert *string
|
||||
SSLKey *string
|
||||
SyncInterval *string
|
||||
AdminPassword *string
|
||||
AdminPasswordFile *string
|
||||
// Deprecated fields
|
||||
Labels *[]Pair
|
||||
Logo *string
|
||||
Templates *string
|
||||
Labels *[]Pair
|
||||
}
|
||||
|
||||
// Status represents the application status.
|
||||
@@ -73,6 +73,7 @@ type (
|
||||
TemplatesURL string `json:"TemplatesURL"`
|
||||
LogoURL string `json:"LogoURL"`
|
||||
BlackListedLabels []Pair `json:"BlackListedLabels"`
|
||||
DisplayDonationHeader bool `json:"DisplayDonationHeader"`
|
||||
DisplayExternalContributors bool `json:"DisplayExternalContributors"`
|
||||
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
LDAPSettings LDAPSettings `json:"LDAPSettings"`
|
||||
@@ -382,16 +383,16 @@ type (
|
||||
StackManager interface {
|
||||
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error
|
||||
Logout(endpoint *Endpoint) error
|
||||
Deploy(stack *Stack, endpoint *Endpoint) error
|
||||
Deploy(stack *Stack, prune bool, endpoint *Endpoint) error
|
||||
Remove(stack *Stack, endpoint *Endpoint) error
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API.
|
||||
APIVersion = "1.15.2"
|
||||
APIVersion = "1.16.1"
|
||||
// DBVersion is the version number of the Portainer database.
|
||||
DBVersion = 6
|
||||
DBVersion = 7
|
||||
// DefaultTemplatesURL represents the default URL for the templates definitions.
|
||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||
)
|
||||
|
||||
+397
-10
@@ -56,7 +56,7 @@ info:
|
||||
|
||||
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).
|
||||
|
||||
version: "1.15.2"
|
||||
version: "1.16.1"
|
||||
title: "Portainer API"
|
||||
contact:
|
||||
email: "info@portainer.io"
|
||||
@@ -77,6 +77,8 @@ tags:
|
||||
description: "Manage Portainer settings"
|
||||
- name: "status"
|
||||
description: "Information about the Portainer instance"
|
||||
- name: "stacks"
|
||||
description: "Manage Docker stacks"
|
||||
- name: "users"
|
||||
description: "Manage users"
|
||||
- name: "teams"
|
||||
@@ -436,6 +438,285 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
|
||||
/endpoints/{endpointId}/stacks:
|
||||
get:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "List stacks"
|
||||
description: |
|
||||
List all stacks based on the current user authorizations.
|
||||
Will return all stacks if using an administrator account otherwise it
|
||||
will only return the list of stacks the user have access to.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackList"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
schema:
|
||||
$ref: "#/definitions/StackListResponse"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Access denied to resource"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
post:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "Deploy a new stack"
|
||||
description: |
|
||||
Deploy a new stack into a Docker environment specified via the endpoint identifier.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackCreate"
|
||||
consumes:
|
||||
- "application/json"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
- name: "method"
|
||||
in: "query"
|
||||
description: "Stack deployment method. Possible values: string or repository."
|
||||
required: true
|
||||
type: "string"
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Stack details. Used when"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/StackCreateRequest"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
schema:
|
||||
$ref: "#/definitions/StackCreateResponse"
|
||||
400:
|
||||
description: "Invalid request"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Invalid request data format"
|
||||
404:
|
||||
description: "Endpoint not found"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Endpoint not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
/endpoints/{endpointId}/stacks/{id}:
|
||||
get:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "Inspect a stack"
|
||||
description: |
|
||||
Retrieve details about a stack.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackInspect"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Stack identifier"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
schema:
|
||||
$ref: "#/definitions/Stack"
|
||||
400:
|
||||
description: "Invalid request"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Invalid request"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
404:
|
||||
description: "Stack not found"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Stack not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
put:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "Update a stack"
|
||||
description: |
|
||||
Update a stack.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackUpdate"
|
||||
consumes:
|
||||
- "application/json"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Stack identifier"
|
||||
required: true
|
||||
type: "string"
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Stack details"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/StackUpdateRequest"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
400:
|
||||
description: "Invalid request"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Invalid request data format"
|
||||
404:
|
||||
description: "Stack not found"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Stack not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
delete:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "Remove a stack"
|
||||
description: |
|
||||
Remove a stack.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackDelete"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Stack identifier"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
400:
|
||||
description: "Invalid request"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Invalid request"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
404:
|
||||
description: "Stack not found"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Stack not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
/endpoints/{endpointId}/stacks/{id}/stackfile:
|
||||
get:
|
||||
tags:
|
||||
- "stacks"
|
||||
summary: "Retrieve the content of the Stack file for the specified stack"
|
||||
description: |
|
||||
Get Stack file content.
|
||||
**Access policy**: restricted
|
||||
operationId: "StackFileInspect"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "endpointId"
|
||||
in: "path"
|
||||
description: "Endpoint identifier"
|
||||
required: true
|
||||
type: "integer"
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Stack identifier"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
schema:
|
||||
$ref: "#/definitions/StackFileInspectResponse"
|
||||
400:
|
||||
description: "Invalid request"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Invalid request"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
404:
|
||||
description: "Stack not found"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Stack not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
/registries:
|
||||
get:
|
||||
tags:
|
||||
@@ -536,7 +817,7 @@ paths:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Endpoint not found"
|
||||
err: "Registry not found"
|
||||
500:
|
||||
description: "Server error"
|
||||
schema:
|
||||
@@ -593,13 +874,6 @@ paths:
|
||||
description: "Server error"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
503:
|
||||
description: "Endpoint management disabled"
|
||||
schema:
|
||||
$ref: "#/definitions/GenericError"
|
||||
examples:
|
||||
application/json:
|
||||
err: "Endpoint management is disabled"
|
||||
delete:
|
||||
tags:
|
||||
- "registries"
|
||||
@@ -1869,7 +2143,7 @@ definitions:
|
||||
description: "Is analytics enabled"
|
||||
Version:
|
||||
type: "string"
|
||||
example: "1.15.2"
|
||||
example: "1.16.1"
|
||||
description: "Portainer API version"
|
||||
PublicSettingsInspectResponse:
|
||||
type: "object"
|
||||
@@ -1880,6 +2154,10 @@ definitions:
|
||||
description: "URL to a logo that will be displayed on the login page as well\
|
||||
\ as on top of the sidebar. Will use default Portainer logo when value is\
|
||||
\ empty string"
|
||||
DisplayDonationHeader:
|
||||
type: "boolean"
|
||||
example: true
|
||||
description: "Whether to display or not the donation message in the header."
|
||||
DisplayExternalContributors:
|
||||
type: "boolean"
|
||||
example: false
|
||||
@@ -1983,6 +2261,10 @@ definitions:
|
||||
\ when querying containers"
|
||||
items:
|
||||
$ref: "#/definitions/Settings_BlackListedLabels"
|
||||
DisplayDonationHeader:
|
||||
type: "boolean"
|
||||
example: true
|
||||
description: "Whether to display or not the donation message in the header."
|
||||
DisplayExternalContributors:
|
||||
type: "boolean"
|
||||
example: false
|
||||
@@ -2398,6 +2680,10 @@ definitions:
|
||||
\ when querying containers"
|
||||
items:
|
||||
$ref: "#/definitions/Settings_BlackListedLabels"
|
||||
DisplayDonationHeader:
|
||||
type: "boolean"
|
||||
example: true
|
||||
description: "Whether to display or not the donation message in the header."
|
||||
DisplayExternalContributors:
|
||||
type: "boolean"
|
||||
example: false
|
||||
@@ -2600,3 +2886,104 @@ definitions:
|
||||
type: "string"
|
||||
example: "nginx:latest"
|
||||
description: "The Docker image associated to the template"
|
||||
StackCreateRequest:
|
||||
type: "object"
|
||||
required:
|
||||
- "Name"
|
||||
- "SwarmID"
|
||||
properties:
|
||||
Name:
|
||||
type: "string"
|
||||
example: "myStack"
|
||||
description: "Name of the stack"
|
||||
SwarmID:
|
||||
type: "string"
|
||||
example: "jpofkc0i9uo9wtx1zesuk649w"
|
||||
description: "Cluster identifier of the Swarm cluster"
|
||||
StackFileContent:
|
||||
type: "string"
|
||||
example: "version: 3\n services:\n web:\n image:nginx"
|
||||
description: "Content of the Stack file. Required when using the 'string' deployment method."
|
||||
GitRepository:
|
||||
type: "string"
|
||||
example: "https://github.com/openfaas/faas"
|
||||
description: "URL of a public Git repository hosting the Stack file. Required when using the 'repository' deployment method."
|
||||
PathInRepository:
|
||||
type: "string"
|
||||
example: "docker-compose.yml"
|
||||
description: "Path to the Stack file inside the Git repository. Required when using the 'repository' deployment method."
|
||||
Env:
|
||||
type: "array"
|
||||
description: "A list of environment variables used during stack deployment"
|
||||
items:
|
||||
$ref: "#/definitions/Stack_Env"
|
||||
Stack_Env:
|
||||
properties:
|
||||
name:
|
||||
type: "string"
|
||||
example: "MYSQL_ROOT_PASSWORD"
|
||||
value:
|
||||
type: "string"
|
||||
example: "password"
|
||||
StackCreateResponse:
|
||||
type: "object"
|
||||
properties:
|
||||
Id:
|
||||
type: "string"
|
||||
example: "myStack_jpofkc0i9uo9wtx1zesuk649w"
|
||||
description: "Id of the stack"
|
||||
StackListResponse:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/Stack"
|
||||
Stack:
|
||||
type: "object"
|
||||
properties:
|
||||
Id:
|
||||
type: "string"
|
||||
example: "myStack_jpofkc0i9uo9wtx1zesuk649w"
|
||||
description: "Stack identifier"
|
||||
Name:
|
||||
type: "string"
|
||||
example: "myStack"
|
||||
description: "Stack name"
|
||||
EntryPoint:
|
||||
type: "string"
|
||||
example: "docker-compose.yml"
|
||||
description: "Path to the Stack file"
|
||||
SwarmID:
|
||||
type: "string"
|
||||
example: "jpofkc0i9uo9wtx1zesuk649w"
|
||||
description: "Cluster identifier of the Swarm cluster where the stack is deployed"
|
||||
ProjectPath:
|
||||
type: "string"
|
||||
example: "/data/compose/myStack_jpofkc0i9uo9wtx1zesuk649w"
|
||||
description: "Path on disk to the repository hosting the Stack file"
|
||||
Env:
|
||||
type: "array"
|
||||
description: "A list of environment variables used during stack deployment"
|
||||
items:
|
||||
$ref: "#/definitions/Stack_Env"
|
||||
StackUpdateRequest:
|
||||
type: "object"
|
||||
properties:
|
||||
StackFileContent:
|
||||
type: "string"
|
||||
example: "version: 3\n services:\n web:\n image:nginx"
|
||||
description: "New content of the Stack file."
|
||||
Env:
|
||||
type: "array"
|
||||
description: "A list of environment variables used during stack deployment"
|
||||
items:
|
||||
$ref: "#/definitions/Stack_Env"
|
||||
Prune:
|
||||
type: "boolean"
|
||||
example: false
|
||||
description: "Prune services that are no longer referenced"
|
||||
StackFileInspectResponse:
|
||||
type: "object"
|
||||
properties:
|
||||
StackFileContent:
|
||||
type: "string"
|
||||
example: "version: 3\n services:\n web:\n image:nginx"
|
||||
description: "Content of the Stack file."
|
||||
|
||||
@@ -5,10 +5,12 @@ angular.module('portainer', [
|
||||
'ngCookies',
|
||||
'ngSanitize',
|
||||
'ngFileUpload',
|
||||
'ngMessages',
|
||||
'angularUtils.directives.dirPagination',
|
||||
'LocalStorageModule',
|
||||
'angular-jwt',
|
||||
'angular-google-analytics',
|
||||
'ui',
|
||||
'angular-loading-bar',
|
||||
'portainer.templates',
|
||||
'portainer.filters',
|
||||
@@ -39,6 +41,7 @@ angular.module('portainer', [
|
||||
'endpointAccess',
|
||||
'endpoints',
|
||||
'events',
|
||||
'extension.storidge',
|
||||
'image',
|
||||
'images',
|
||||
'initAdmin',
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ angular.module('portainer')
|
||||
StateManager.initialize()
|
||||
.then(function success(state) {
|
||||
if (state.application.authentication) {
|
||||
initAuthentication(authManager, Authentication, $rootScope);
|
||||
initAuthentication(authManager, Authentication, $rootScope, $state);
|
||||
}
|
||||
if (state.application.analytics) {
|
||||
initAnalytics(Analytics, $rootScope);
|
||||
@@ -30,7 +30,7 @@ angular.module('portainer')
|
||||
}]);
|
||||
|
||||
|
||||
function initAuthentication(authManager, Authentication, $rootScope) {
|
||||
function initAuthentication(authManager, Authentication, $rootScope, $state) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
authManager.redirectWhenUnauthenticated();
|
||||
Authentication.init();
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="About">
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
About Portainer
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<b>Portainer</b> is a <a href="https://github.com/portainer/portainer/blob/develop/LICENSE" target="_blank" >free and open-source software</a> brought to you with <span class="menu-icon fa fa-heart red-icon "></span> by <a href="https://portainer.io/" target="_blank">portainer.io</a> and <a href="https://github.com/portainer/portainer/graphs/contributors" target="_blank">contributors.</a>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header title="Help support portainer" icon="fa-heartbeat"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="small" style="line-height:1.65;">
|
||||
<p>
|
||||
It is a community effort to make <b>Portainer</b> as feature-rich as simple it is to deploy and use. We need all the help we can get!
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Fund our work</u>
|
||||
<ul>
|
||||
<li>Become a <a href="https://www.patreon.com/Portainerio" target="_blank"><i class="fa fa-money" aria-hidden="true"></i> patron</a></li>
|
||||
<li>Consider a <a href="https://portainer.io/support.html" target="_blank">paid support plan</a></li>
|
||||
<li>Make a <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6" target="_blank"><i class="fa fa-paypal" aria-hidden="true"></i> donation</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Contribute</u>
|
||||
<ul>
|
||||
<li>Found a bug or got a feature idea? Let's talk about it on <a href="https://github.com/portainer/portainer/issues" target="_blank"><i class="fa fa-github" aria-hidden="true"></i> Github</a></li>
|
||||
<li>Follow our <a href="https://portainer.readthedocs.io/en/latest/contribute.html" target="_blank">contribution guidelines</a> to build it locally and make a <a target="_blank" href="https://github.com/portainer/portainer/compare"><i class="fa fa-github" aria-hidden="true"></i> pull request</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Spread the word</u>
|
||||
<ul>
|
||||
<li>Talk to your friends and colleagues about how awesome Portainer is!</li>
|
||||
<li>Follow us on <a href="https://twitter.com/portainerio" target="_blank"><i class="fa fa-twitter" aria-hidden="true"></i> Twitter</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header title="Support and services" icon="fa-building-o"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="small" style="line-height:1.65;">
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Documentation</u>
|
||||
<ul>
|
||||
<li>Checkout our <a target="_blank" href="http://portainer.readthedocs.io"><i class="fa fa-book" aria-hidden="true"></i> online documentation</a></li>
|
||||
<li>Be sure to have a look at our <a href="https://portainer.readthedocs.io/en/latest/faq.html" target="_blank">FAQ</a> and our list of <a href="https://github.com/portainer/portainer/issues" target="_blank">open issues</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Community support</u>
|
||||
<ul>
|
||||
<li>Join us on <a href="https://portainer.io/slack/" target="_blank"><i class="fa fa-slack" aria-hidden="true"></i> Slack</a></li>
|
||||
<li>We're also on <a href="https://gitter.im/portainer/Lobby" target="_blank"><i class="fa fa-github-alt" aria-hidden="true"></i> Gitter</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Services</u>
|
||||
<ul>
|
||||
<li>Find out more about our <a href="https://portainer.io/support.html" target="_blank">consulting and commercial support plans</a></li>
|
||||
<li>We also propose a fund-a-feature plan, reach out to us at <a target="_blank" href="mailto:info@portainer.io"><i class="fa fa-envelope-o" aria-hidden="true"></i> portainer.io</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header title="Limitations" icon="fa-plug"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="small">
|
||||
Portainer has full support for Docker >=1.10 and Docker Swarm >= 1.2.3, and partial support for Docker 1.9 (some features may not be available).
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,73 +8,13 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-file-code-o" title="Configs">
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.config"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add config</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('CreatedAt')">
|
||||
Created at
|
||||
<span ng-show="sortType == 'CreatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'CreatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="config in (state.filteredConfigs = ( configs | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))">
|
||||
<td><input type="checkbox" ng-model="config.Checked" ng-change="selectItem(config)"/></td>
|
||||
<td><a ui-sref="config({id: config.Id})">{{ config.Name }}</a></td>
|
||||
<td>{{ config.CreatedAt | getisodate }}</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="config.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ config.ResourceControl.Ownership ? config.ResourceControl.Ownership : config.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!configs">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="configs.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No configs available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="configs" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
<rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<configs-datatable
|
||||
title="Configs" title-icon="fa-file-code-o"
|
||||
dataset="configs" table-key="configs"
|
||||
order-by="Name" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
></configs-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,25 @@
|
||||
angular.module('configs', [])
|
||||
.controller('ConfigsController', ['$scope', '$stateParams', '$state', 'ConfigService', 'Notifications', 'Pagination',
|
||||
function ($scope, $stateParams, $state, ConfigService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('configs');
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredConfigs, function (config) {
|
||||
if (config.Checked !== allSelected) {
|
||||
config.Checked = allSelected;
|
||||
$scope.selectItem(config);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
angular.forEach($scope.configs, function (config) {
|
||||
if (config.Checked) {
|
||||
ConfigService.remove(config.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Config deleted', config.Id);
|
||||
var index = $scope.configs.indexOf(config);
|
||||
$scope.configs.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove config');
|
||||
});
|
||||
}
|
||||
.controller('ConfigsController', ['$scope', '$state', 'ConfigService', 'Notifications',
|
||||
function ($scope, $state, ConfigService, Notifications) {
|
||||
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (config) {
|
||||
ConfigService.remove(config.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Config successfully removed', config.Name);
|
||||
var index = $scope.configs.indexOf(config);
|
||||
$scope.configs.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove config');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -12,15 +12,20 @@
|
||||
<rd-widget-header icon="fa-cogs" title="Actions"></rd-widget-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button class="btn btn-success" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
|
||||
<button class="btn btn-danger" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
|
||||
<button class="btn btn-danger" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
|
||||
<button class="btn btn-primary" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
|
||||
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||
<button class="btn btn-danger" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<button class="btn btn-danger" ng-click="recreate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</button>
|
||||
<button class="btn btn-primary" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
|
||||
<button class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
|
||||
<button class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
|
||||
<button class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||
<button class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress" ng-if="!container.Config.Labels['com.docker.swarm.service.id']">
|
||||
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</span>
|
||||
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
@@ -79,10 +84,10 @@
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
|
||||
<a class="btn" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
|
||||
<a class="btn" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
|
||||
<a class="btn" type="button" ui-sref="inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -265,70 +270,16 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-sitemap" title="Connected networks">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Network Name</th>
|
||||
<th>IP Address</th>
|
||||
<th>Gateway</th>
|
||||
<th>MacAddress</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="(key, value) in container.NetworkSettings.Networks | itemsPerPage: state.pagination_count">
|
||||
<td><a ui-sref="network({id: value.NetworkID})">{{ key }}</a></td>
|
||||
<td>{{ value.IPAddress || '-' }}</td>
|
||||
<td>{{ value.Gateway || '-' }}</td>
|
||||
<td>{{ value.MacAddress || '-' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-danger" ng-disabled="state.leaveNetworkInProgress" button-spinner="state.leaveNetworkInProgress" ng-click="containerLeaveNetwork(container, value.NetworkID)">
|
||||
<span ng-hide="state.leaveNetworkInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i> Leave network</span>
|
||||
<span ng-show="state.leaveNetworkInProgress">Leaving network...</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="(container.NetworkSettings.Networks | emptyobject)">
|
||||
<td colspan="5" class="text-center text-muted">No networks connected.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
<hr />
|
||||
<form class="form-horizontal">
|
||||
<!-- network-input -->
|
||||
<div class="row">
|
||||
<label for="container_network" class="col-sm-3 col-lg-2 control-label text-left">Join a Network</label>
|
||||
<div class="col-sm-5 col-lg-4">
|
||||
<select class="form-control" ng-model="selectedNetwork" id="container_network">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in availableNetworks" ng-value="net.Id">{{ net.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.joinNetworkInProgress || !selectedNetwork" ng-click="containerJoinNetwork(container, selectedNetwork)" button-spinner="state.joinNetworkInProgress">
|
||||
<span ng-hide="state.joinNetworkInProgress">Join network</span>
|
||||
<span ng-show="state.joinNetworkInProgress">Joining network...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<container-networks-datatable
|
||||
title="Connected networks" title-icon="fa-sitemap"
|
||||
dataset="container.NetworkSettings.Networks" table-key="container-networks"
|
||||
container="container"
|
||||
available-networks="availableNetworks"
|
||||
join-network-action="containerJoinNetwork"
|
||||
join-network-action-in-progress="state.joinNetworkInProgress"
|
||||
leave-network-action="containerLeaveNetwork"
|
||||
leave-network-action-in-progress="state.leaveNetworkInProgress"
|
||||
></container-networks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
angular.module('container', [])
|
||||
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'Pagination', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
|
||||
function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, Pagination, ModalService, ResourceControlService, RegistryService, ImageService) {
|
||||
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
|
||||
function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService) {
|
||||
$scope.activityTime = 0;
|
||||
$scope.portBindings = [];
|
||||
|
||||
$scope.config = {
|
||||
Image: '',
|
||||
Registry: ''
|
||||
};
|
||||
$scope.state = {
|
||||
joinNetworkInProgress: false,
|
||||
leaveNetworkInProgress: false,
|
||||
pagination_count: Pagination.getPaginationCount('container_networks')
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('container_networks', $scope.state.pagination_count);
|
||||
$scope.state = {
|
||||
recreateContainerInProgress: false,
|
||||
joinNetworkInProgress: false,
|
||||
leaveNetworkInProgress: false
|
||||
};
|
||||
|
||||
var update = function () {
|
||||
@@ -207,6 +205,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit,
|
||||
function recreateContainer(pullImage) {
|
||||
var container = $scope.container;
|
||||
var config = ContainerHelper.configFromContainer(container.Model);
|
||||
$scope.state.recreateContainerInProgress = true;
|
||||
ContainerService.remove(container, true)
|
||||
.then(function success() {
|
||||
return RegistryService.retrieveRegistryFromRepository(container.Config.Image);
|
||||
@@ -239,6 +238,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit,
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to re-create container');
|
||||
$scope.state.recreateContainerInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('containerLogs', [])
|
||||
.controller('ContainerLogsController', ['$scope', '$transition$', '$anchorScroll', 'ContainerLogs', 'Container',
|
||||
function ($scope, $transition$, $anchorScroll, ContainerLogs, Container) {
|
||||
.controller('ContainerLogsController', ['$scope', '$transition$', '$anchorScroll', 'ContainerLogs', 'Container', 'Notifications',
|
||||
function ($scope, $transition$, $anchorScroll, ContainerLogs, Container, Notifications) {
|
||||
$scope.state = {};
|
||||
$scope.state.displayTimestampsOut = false;
|
||||
$scope.state.displayTimestampsErr = false;
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
|
||||
<div class="row">
|
||||
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
|
||||
<!-- <div class="col-lg-4 col-md-6 col-sm-12"> -->
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-area-chart" title="Memory usage"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
@@ -81,49 +80,13 @@
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider !== 'VMWARE_VIC'">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Processes">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="title in processInfo.Titles">
|
||||
<a ng-click="order(title)">
|
||||
{{ title }}
|
||||
<span ng-show="sortType == title && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == title && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="processDetails in state.filteredProcesses = (processInfo.Processes | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
|
||||
<td ng-repeat="procInfo in processDetails track by $index">{{ procInfo }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!processInfo.Processes">
|
||||
<td colspan="processInfo.Titles.length" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="state.filteredProcesses.length === 0">
|
||||
<td colspan="processInfo.Titles.length" class="text-center text-muted">No processes available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="processInfo.Processes" class="pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<container-processes-datatable
|
||||
title="Processes" title-icon="fa-tasks"
|
||||
dataset="processInfo.Processes" headerset="processInfo.Titles"
|
||||
table-key="container-processes"
|
||||
show-text-filter="true"
|
||||
></container-processes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
angular.module('containerStats', [])
|
||||
.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'Pagination',
|
||||
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, Pagination) {
|
||||
.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications',
|
||||
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications) {
|
||||
|
||||
$scope.state = {
|
||||
refreshRate: '5',
|
||||
networkStatsUnavailable: false
|
||||
};
|
||||
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('stats_processes');
|
||||
$scope.sortType = 'CMD';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('stats_processes', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
stopRepeater();
|
||||
});
|
||||
|
||||
@@ -7,145 +7,23 @@
|
||||
<rd-header-content>Containers</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-server" title="Containers">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-success btn-responsive" ng-click="startAction()" ng-disabled="!state.selectedItemCount || state.noStoppedItemsSelected"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
|
||||
<button type="button" class="btn btn-danger btn-responsive" ng-click="stopAction()" ng-disabled="!state.selectedItemCount || state.noRunningItemsSelected"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
|
||||
<button type="button" class="btn btn-danger btn-responsive" ng-click="killAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="restartAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount || state.noRunningItemsSelected"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount || state.noPausedItemsSelected"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||
<button type="button" class="btn btn-danger btn-responsive" ng-click="confirmRemoveAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.container"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add container</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="checkbox" ng-model="state.displayAll" id="displayAll" ng-change="toggleGetAll()" style="margin-top: -2px; margin-right: 5px;"/><label for="displayAll">Show all containers</label>
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Status')">
|
||||
State
|
||||
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Names')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Names' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
<a data-toggle="tooltip" title="More" ng-click="truncateMore();" ng-show="showMore">
|
||||
<i class="fa fa-plus-square" aria-hidden="true"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('StackName')">
|
||||
Stack
|
||||
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Image')">
|
||||
Image
|
||||
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="state.displayIP">
|
||||
<a ng-click="order('IP')">
|
||||
IP Address
|
||||
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
|
||||
<a ng-click="order('Host')">
|
||||
Host IP
|
||||
<span ng-show="sortType == 'Host' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Host' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Ports')">
|
||||
Published Ports
|
||||
<span ng-show="sortType == 'Ports' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Ports' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
|
||||
<td>
|
||||
<span ng-if="['starting','healthy','unhealthy'].indexOf(container.Status) !== -1" class="label label-{{ container.Status|containerstatusbadge }} interactive" uib-tooltip="This container has a health check">{{ container.Status }}</span>
|
||||
<span ng-if="['starting','healthy','unhealthy'].indexOf(container.Status) === -1" class="label label-{{ container.Status|containerstatusbadge }}">{{ container.Status }}</span>
|
||||
</td>
|
||||
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername|truncate: truncate_size}}</a></td>
|
||||
<td ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|containername|truncate: truncate_size}}</a></td>
|
||||
<td>{{ container.StackName ? container.StackName : '-' }}</td>
|
||||
<td><a ui-sref="image({id: container.Image})">{{ container.Image | hideshasum }}</a></td>
|
||||
<td ng-if="state.displayIP">{{ container.IP ? container.IP : '-' }}</td>
|
||||
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ container.hostIP }}</td>
|
||||
<td>
|
||||
<a ng-if="container.Ports.length > 0" ng-repeat="p in container.Ports" class="image-tag" ng-href="http://{{ PublicURL || p.host }}:{{p.public}}" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i> {{p.public}}:{{ p.private }}
|
||||
</a>
|
||||
<span ng-if="container.Ports.length == 0" >-</span>
|
||||
</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="container.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ container.ResourceControl.Ownership ? container.ResourceControl.Ownership : container.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!containers">
|
||||
<td colspan="9" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="containers.length == 0">
|
||||
<td colspan="9" class="text-center text-muted">No containers available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="containers" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="containers">
|
||||
<containers-datatable
|
||||
title="Containers" title-icon="fa-server"
|
||||
dataset="containers" table-key="containers"
|
||||
order-by="Status" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
swarm-containers="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"
|
||||
public-url="state.publicURL"
|
||||
container-name-truncate-size="truncate_size"
|
||||
start-action="startAction"
|
||||
stop-action="stopAction"
|
||||
restart-action="restartAction"
|
||||
kill-action="killAction"
|
||||
pause-action="pauseAction"
|
||||
resume-action="resumeAction"
|
||||
remove-action="confirmRemoveAction"
|
||||
></containers-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,219 +1,108 @@
|
||||
angular.module('containers', [])
|
||||
.controller('ContainersController', ['$q', '$scope', '$state', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'SystemService', 'Notifications', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider', 'LocalStorage',
|
||||
function ($q, $scope, $state, $filter, Container, ContainerService, ContainerHelper, SystemService, Notifications, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider, LocalStorage) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('containers');
|
||||
$scope.state.displayAll = LocalStorage.getFilterContainerShowAll();
|
||||
$scope.state.displayIP = false;
|
||||
$scope.sortType = 'State';
|
||||
$scope.sortReverse = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.truncate_size = 40;
|
||||
$scope.showMore = true;
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
$scope.PublicURL = EndpointProvider.endpointPublicURL();
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('containers', $scope.state.pagination_count);
|
||||
.controller('ContainersController', ['$q', '$scope', '$state', '$filter', '$transition$', 'ContainerService', 'SystemService', 'Notifications', 'ModalService', 'EndpointProvider',
|
||||
function ($q, $scope, $state, $filter, $transition$, ContainerService, SystemService, Notifications, ModalService, EndpointProvider) {
|
||||
$scope.state = {
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
|
||||
$scope.cleanAssociatedVolumes = false;
|
||||
|
||||
var update = function (data) {
|
||||
$scope.state.selectedItemCount = 0;
|
||||
Container.query(data, function (d) {
|
||||
var containers = d;
|
||||
$scope.containers = containers.map(function (container) {
|
||||
var model = new ContainerViewModel(container);
|
||||
model.Status = $filter('containerstatus')(model.Status);
|
||||
|
||||
EntityListService.rememberPreviousSelection($scope.containers, model, function onSelect(model){
|
||||
$scope.selectItem(model);
|
||||
});
|
||||
|
||||
if (model.IP) {
|
||||
$scope.state.displayIP = true;
|
||||
}
|
||||
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
|
||||
model.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
|
||||
}
|
||||
return model;
|
||||
});
|
||||
updateSelectionFlags();
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve containers');
|
||||
$scope.containers = [];
|
||||
});
|
||||
$scope.startAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully started';
|
||||
var errorMessage = 'Unable to start container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
var batch = function (items, action, msg) {
|
||||
var counter = 0;
|
||||
var complete = function () {
|
||||
counter = counter - 1;
|
||||
if (counter === 0) {
|
||||
update({all: $scope.state.displayAll ? 1 : 0});
|
||||
}
|
||||
};
|
||||
angular.forEach(items, function (c) {
|
||||
if (c.Checked) {
|
||||
counter = counter + 1;
|
||||
if (action === Container.start) {
|
||||
action({id: c.Id}, {}, function (d) {
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to start container');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else if (action === Container.remove) {
|
||||
ContainerService.remove(c, $scope.cleanAssociatedVolumes)
|
||||
.then(function success() {
|
||||
var index = items.indexOf(c);
|
||||
items.splice(index, 1);
|
||||
Notifications.success('Container successfully removed');
|
||||
complete();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else if (action === Container.pause) {
|
||||
action({id: c.Id}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.success('Container is already paused', c.Id);
|
||||
} else {
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to pause container');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else {
|
||||
action({id: c.Id}, function (d) {
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'An error occured');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
$scope.stopAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully stopped';
|
||||
var errorMessage = 'Unable to stop container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredContainers, function (container) {
|
||||
if (container.Checked !== allSelected) {
|
||||
container.Checked = allSelected;
|
||||
toggleItemSelection(container);
|
||||
}
|
||||
});
|
||||
updateSelectionFlags();
|
||||
$scope.restartAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully restarted';
|
||||
var errorMessage = 'Unable to restart container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
toggleItemSelection(item);
|
||||
updateSelectionFlags();
|
||||
$scope.killAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully killed';
|
||||
var errorMessage = 'Unable to kill container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.toggleGetAll = function () {
|
||||
LocalStorage.storeFilterContainerShowAll($scope.state.displayAll);
|
||||
update({all: $scope.state.displayAll ? 1 : 0});
|
||||
$scope.pauseAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully paused';
|
||||
var errorMessage = 'Unable to pause container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.startAction = function () {
|
||||
batch($scope.containers, Container.start, 'Started');
|
||||
$scope.resumeAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully resumed';
|
||||
var errorMessage = 'Unable to resume container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.stopAction = function () {
|
||||
batch($scope.containers, Container.stop, 'Stopped');
|
||||
};
|
||||
|
||||
$scope.restartAction = function () {
|
||||
batch($scope.containers, Container.restart, 'Restarted');
|
||||
};
|
||||
|
||||
$scope.killAction = function () {
|
||||
batch($scope.containers, Container.kill, 'Killed');
|
||||
};
|
||||
|
||||
$scope.pauseAction = function () {
|
||||
batch($scope.containers, Container.pause, 'Paused');
|
||||
};
|
||||
|
||||
$scope.unpauseAction = function () {
|
||||
batch($scope.containers, Container.unpause, 'Unpaused');
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
batch($scope.containers, Container.remove, 'Removed');
|
||||
};
|
||||
|
||||
|
||||
$scope.truncateMore = function(size) {
|
||||
$scope.truncate_size = 80;
|
||||
$scope.showMore = false;
|
||||
};
|
||||
|
||||
$scope.confirmRemoveAction = function () {
|
||||
$scope.confirmRemoveAction = function(selectedItems) {
|
||||
var isOneContainerRunning = false;
|
||||
angular.forEach($scope.containers, function (c) {
|
||||
if (c.Checked && c.State === 'running') {
|
||||
for (var i = 0; i < selectedItems.length; i++) {
|
||||
var container = selectedItems[i];
|
||||
if (container.State === 'running') {
|
||||
isOneContainerRunning = true;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var title = 'You are about to remove one or more container.';
|
||||
if (isOneContainerRunning) {
|
||||
title = 'You are about to remove one or more running containers.';
|
||||
title = 'You are about to remove one or more running container.';
|
||||
}
|
||||
ModalService.confirmContainerDeletion(
|
||||
title,
|
||||
function (result) {
|
||||
|
||||
ModalService.confirmContainerDeletion(title, function (result) {
|
||||
if(!result) { return; }
|
||||
$scope.cleanAssociatedVolumes = false;
|
||||
var cleanVolumes = false;
|
||||
if (result[0]) {
|
||||
$scope.cleanAssociatedVolumes = true;
|
||||
cleanVolumes = true;
|
||||
}
|
||||
$scope.removeAction();
|
||||
removeAction(selectedItems, cleanVolumes);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function toggleItemSelection(item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
function executeActionOnContainerList(containers, action, successMessage, errorMessage) {
|
||||
var actionCount = containers.length;
|
||||
angular.forEach(containers, function (container) {
|
||||
action(container.Id)
|
||||
.then(function success() {
|
||||
Notifications.success(successMessage, container.Names[0]);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, errorMessage);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.transitionTo($state.current, { selectedContainers: containers }, { reload: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectionFlags() {
|
||||
$scope.state.noStoppedItemsSelected = true;
|
||||
$scope.state.noRunningItemsSelected = true;
|
||||
$scope.state.noPausedItemsSelected = true;
|
||||
$scope.containers.forEach(function(container) {
|
||||
if(!container.Checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(container.Status === 'paused') {
|
||||
$scope.state.noPausedItemsSelected = false;
|
||||
} else if(container.Status === 'stopped' ||
|
||||
container.Status === 'created') {
|
||||
$scope.state.noStoppedItemsSelected = false;
|
||||
} else if(container.Status === 'running') {
|
||||
$scope.state.noRunningItemsSelected = false;
|
||||
}
|
||||
} );
|
||||
function removeAction(containers, cleanVolumes) {
|
||||
var actionCount = containers.length;
|
||||
angular.forEach(containers, function (container) {
|
||||
ContainerService.remove(container, cleanVolumes)
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully removed', container.Names[0]);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function retrieveSwarmHostsInfo(data) {
|
||||
@@ -231,17 +120,42 @@ angular.module('containers', [])
|
||||
return swarm_hosts;
|
||||
}
|
||||
|
||||
function assignContainers(containers, provider) {
|
||||
var previouslySelectedContainers = $transition$.params().selectedContainers || [];
|
||||
$scope.containers = containers.map(function (container) {
|
||||
container.Status = $filter('containerstatus')(container.Status);
|
||||
if (provider === 'DOCKER_SWARM') {
|
||||
container.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
|
||||
}
|
||||
|
||||
var previousContainer = _.find(previouslySelectedContainers, function(item) {
|
||||
return item.Id === container.Id;
|
||||
});
|
||||
|
||||
if (previousContainer && previousContainer.Checked) {
|
||||
container.Checked = true;
|
||||
}
|
||||
|
||||
return container;
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||
$q.when(provider !== 'DOCKER_SWARM' || SystemService.info())
|
||||
|
||||
$q.all({
|
||||
swarm: provider !== 'DOCKER_SWARM' || SystemService.info(),
|
||||
containers: ContainerService.containers(1)
|
||||
})
|
||||
.then(function success(data) {
|
||||
if (provider === 'DOCKER_SWARM') {
|
||||
$scope.swarm_hosts = retrieveSwarmHostsInfo(data);
|
||||
$scope.swarm_hosts = retrieveSwarmHostsInfo(data.swarm);
|
||||
}
|
||||
update({all: $scope.state.displayAll ? 1 : 0});
|
||||
assignContainers(data.containers, provider);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
$scope.containers = [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ function ($scope, $state, $document, Notifications, ConfigService, Authenticatio
|
||||
$scope.create = function () {
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
|
||||
@@ -11,6 +11,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
NetworkContainer: '',
|
||||
Labels: [],
|
||||
ExtraHosts: [],
|
||||
MacAddress: '',
|
||||
IPv4: '',
|
||||
IPv6: '',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
@@ -34,6 +35,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
Image: '',
|
||||
Env: [],
|
||||
Cmd: '',
|
||||
MacAddress: '',
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
@@ -193,6 +195,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
config.Hostname = '';
|
||||
}
|
||||
config.HostConfig.NetworkMode = networkMode;
|
||||
config.MacAddress = $scope.formValues.MacAddress;
|
||||
|
||||
config.NetworkingConfig.EndpointsConfig[networkMode] = {
|
||||
IPAMConfig: {
|
||||
@@ -349,6 +352,12 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
}
|
||||
}
|
||||
|
||||
$scope.resetNetworkConfig = function() {
|
||||
$scope.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
};
|
||||
|
||||
function loadFromContainerNetworkConfig(d) {
|
||||
$scope.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
@@ -381,6 +390,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
}
|
||||
}
|
||||
$scope.config.NetworkingConfig.EndpointsConfig[$scope.config.HostConfig.NetworkMode] = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode];
|
||||
// Mac Address
|
||||
$scope.formValues.MacAddress = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].MacAddress;
|
||||
// ExtraHosts
|
||||
for (var h in $scope.config.HostConfig.ExtraHosts) {
|
||||
if ({}.hasOwnProperty.call($scope.config.HostConfig.ExtraHosts, h)) {
|
||||
@@ -390,7 +401,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromContainerEnvrionmentVariables(d) {
|
||||
function loadFromContainerEnvironmentVariables(d) {
|
||||
var envArr = [];
|
||||
for (var e in $scope.config.Env) {
|
||||
if ({}.hasOwnProperty.call($scope.config.Env, e)) {
|
||||
@@ -472,7 +483,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
loadFromContainerPortBindings(d);
|
||||
loadFromContainerVolumes(d);
|
||||
loadFromContainerNetworkConfig(d);
|
||||
loadFromContainerEnvrionmentVariables(d);
|
||||
loadFromContainerEnvironmentVariables(d);
|
||||
loadFromContainerLabels(d);
|
||||
loadFromContainerConsole(d);
|
||||
loadFromContainerDevices(d);
|
||||
@@ -550,7 +561,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
});
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.isAdmin = userDetails.role === 1 ? true : false;
|
||||
$scope.isAdmin = userDetails.role === 1;
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
@@ -574,7 +585,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<form class="form-horizontal" autocomplete="off">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
@@ -28,7 +28,7 @@
|
||||
<div ng-if="formValues.Registry || !fromContainer">
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="config.Image" registry="formValues.Registry" ng-if="formValues.Registry"></por-image-registry>
|
||||
<por-image-registry image="config.Image" registry="formValues.Registry" ng-if="formValues.Registry" auto-complete="true"></por-image-registry>
|
||||
</div>
|
||||
<!-- !image-and-registry -->
|
||||
<!-- always-pull -->
|
||||
@@ -296,7 +296,7 @@
|
||||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-2 col-lg-1 control-label text-left">Network</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" ng-model="config.HostConfig.NetworkMode" id="container_network">
|
||||
<select class="form-control" ng-model="config.HostConfig.NetworkMode" id="container_network" ng-change="resetNetworkConfig()">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in availableNetworks" ng-value="net.Name">{{ net.Name }}</option>
|
||||
</select>
|
||||
@@ -332,6 +332,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- !domainname -->
|
||||
<!-- mac-address-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_macaddress" class="col-sm-2 col-lg-1 control-label text-left">Mac Address</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !mac-address-input -->
|
||||
<!-- ipv4-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_ipv4" class="col-sm-2 col-lg-1 control-label text-left">IPv4 Address</label>
|
||||
@@ -523,7 +531,7 @@
|
||||
Memory reservation
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation">
|
||||
@@ -541,7 +549,7 @@
|
||||
Memory limit
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit">
|
||||
@@ -559,7 +567,7 @@
|
||||
CPU limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<por-slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
|
||||
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
|
||||
</div>
|
||||
<div class="col-sm-4" style="margin-top: 20px;">
|
||||
<p class="small text-muted">
|
||||
|
||||
@@ -93,7 +93,7 @@ function ($q, $scope, $state, PluginService, Notifications, NetworkService, Labe
|
||||
var networkConfiguration = prepareConfiguration();
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
|
||||
@@ -59,7 +59,7 @@ function ($scope, $state, Notifications, SecretService, LabelHelper, Authenticat
|
||||
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||
// See app/components/templates/templatesController.js as a reference.
|
||||
angular.module('createService', [])
|
||||
.controller('CreateServiceController', ['$q', '$scope', '$state', '$timeout', 'Service', 'ServiceHelper', 'ConfigService', 'ConfigHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'RegistryService', 'HttpRequestHelper', 'NodeService', 'SettingsService',
|
||||
function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, ConfigHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, RegistryService, HttpRequestHelper, NodeService, SettingsService) {
|
||||
.controller('CreateServiceController', ['$q', '$scope', '$state', '$timeout', 'Service', 'ServiceHelper', 'ConfigService', 'ConfigHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'PluginService', 'RegistryService', 'HttpRequestHelper', 'NodeService', 'SettingsService',
|
||||
function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, ConfigHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, PluginService, RegistryService, HttpRequestHelper, NodeService, SettingsService) {
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
@@ -20,11 +20,12 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
Volumes: [],
|
||||
Network: '',
|
||||
ExtraNetworks: [],
|
||||
HostsEntries: [],
|
||||
Ports: [],
|
||||
Parallelism: 1,
|
||||
PlacementConstraints: [],
|
||||
PlacementPreferences: [],
|
||||
UpdateDelay: 0,
|
||||
UpdateDelay: '0s',
|
||||
UpdateOrder: 'stop-first',
|
||||
FailureAction: 'pause',
|
||||
Secrets: [],
|
||||
@@ -35,7 +36,13 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
MemoryLimit: 0,
|
||||
MemoryReservation: 0,
|
||||
MemoryLimitUnit: 'MB',
|
||||
MemoryReservationUnit: 'MB'
|
||||
MemoryReservationUnit: 'MB',
|
||||
RestartCondition: 'any',
|
||||
RestartDelay: '5s',
|
||||
RestartMaxAttempts: 0,
|
||||
RestartWindow: '0s',
|
||||
LogDriverName: '',
|
||||
LogDriverOpts: []
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
@@ -65,6 +72,14 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
$scope.formValues.ExtraNetworks.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addHostsEntry = function() {
|
||||
$scope.formValues.HostsEntries.push({});
|
||||
};
|
||||
|
||||
$scope.removeHostsEntry = function(index) {
|
||||
$scope.formValues.HostsEntries.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addVolume = function() {
|
||||
$scope.formValues.Volumes.push({ Source: '', Target: '', ReadOnly: false, Type: 'volume' });
|
||||
};
|
||||
@@ -82,7 +97,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
};
|
||||
|
||||
$scope.addSecret = function() {
|
||||
$scope.formValues.Secrets.push({});
|
||||
$scope.formValues.Secrets.push({ overrideTarget: false });
|
||||
};
|
||||
|
||||
$scope.removeSecret = function(index) {
|
||||
@@ -129,6 +144,14 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
$scope.formValues.ContainerLabels.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLogDriverOpt = function(value) {
|
||||
$scope.formValues.LogDriverOpts.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeLogDriverOpt = function(index) {
|
||||
$scope.formValues.LogDriverOpts.splice(index, 1);
|
||||
};
|
||||
|
||||
function prepareImageConfig(config, input) {
|
||||
var imageConfig = ImageHelper.createImageConfigForContainer(input.Image, input.Registry.URL);
|
||||
config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
||||
@@ -240,15 +263,40 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
config.Networks = _.uniqWith(networks, _.isEqual);
|
||||
}
|
||||
|
||||
function prepareHostsEntries(config, input) {
|
||||
var hostsEntries = [];
|
||||
if (input.HostsEntries) {
|
||||
input.HostsEntries.forEach(function (host_ip) {
|
||||
if (host_ip.value && host_ip.value.indexOf(':') && host_ip.value.split(':').length === 2) {
|
||||
var keyVal = host_ip.value.split(':');
|
||||
// Hosts file format, IP_address canonical_hostname
|
||||
hostsEntries.push(keyVal[1] + ' ' + keyVal[0]);
|
||||
}
|
||||
});
|
||||
if (hostsEntries.length > 0) {
|
||||
config.TaskTemplate.ContainerSpec.Hosts = hostsEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareUpdateConfig(config, input) {
|
||||
config.UpdateConfig = {
|
||||
Parallelism: input.Parallelism || 0,
|
||||
Delay: input.UpdateDelay || 0,
|
||||
Delay: ServiceHelper.translateHumanDurationToNanos(input.UpdateDelay) || 0,
|
||||
FailureAction: input.FailureAction,
|
||||
Order: input.UpdateOrder
|
||||
};
|
||||
}
|
||||
|
||||
function prepareRestartPolicy(config, input) {
|
||||
config.TaskTemplate.RestartPolicy = {
|
||||
Condition: input.RestartCondition || 'any',
|
||||
Delay: ServiceHelper.translateHumanDurationToNanos(input.RestartDelay) || 5000000000,
|
||||
MaxAttempts: input.RestartMaxAttempts || 0,
|
||||
Window: ServiceHelper.translateHumanDurationToNanos(input.RestartWindow) || 0
|
||||
};
|
||||
}
|
||||
|
||||
function preparePlacementConfig(config, input) {
|
||||
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints);
|
||||
config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences);
|
||||
@@ -275,6 +323,9 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
if (secret.model) {
|
||||
var s = SecretHelper.secretConfig(secret.model);
|
||||
s.File.Name = s.SecretName;
|
||||
if (secret.overrideTarget && secret.target && secret.target !== '') {
|
||||
s.File.Name = secret.target;
|
||||
}
|
||||
secrets.push(s);
|
||||
}
|
||||
});
|
||||
@@ -314,6 +365,23 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
}
|
||||
}
|
||||
|
||||
function prepareLogDriverConfig(config, input) {
|
||||
var logOpts = {};
|
||||
if (input.LogDriverName) {
|
||||
config.TaskTemplate.LogDriver = { Name: input.LogDriverName };
|
||||
if (input.LogDriverName !== 'none') {
|
||||
input.LogDriverOpts.forEach(function (opt) {
|
||||
if (opt.name) {
|
||||
logOpts[opt.name] = opt.value;
|
||||
}
|
||||
});
|
||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||
config.TaskTemplate.LogDriver.Options = logOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
var input = $scope.formValues;
|
||||
var config = {
|
||||
@@ -339,12 +407,15 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
prepareLabelsConfig(config, input);
|
||||
prepareVolumes(config, input);
|
||||
prepareNetworks(config, input);
|
||||
prepareHostsEntries(config, input);
|
||||
prepareUpdateConfig(config, input);
|
||||
prepareConfigConfig(config, input);
|
||||
prepareSecretConfig(config, input);
|
||||
preparePlacementConfig(config, input);
|
||||
prepareResourcesCpuConfig(config, input);
|
||||
prepareResourcesMemoryConfig(config, input);
|
||||
prepareRestartPolicy(config, input);
|
||||
prepareLogDriverConfig(config, input);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -387,7 +458,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
@@ -431,19 +502,19 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
|
||||
configs: apiVersion >= 1.30 ? ConfigService.configs() : [],
|
||||
nodes: NodeService.nodes(),
|
||||
settings: SettingsService.publicSettings()
|
||||
settings: SettingsService.publicSettings(),
|
||||
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25)
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.availableVolumes = data.volumes;
|
||||
$scope.availableNetworks = data.networks;
|
||||
$scope.availableSecrets = data.secrets;
|
||||
$scope.availableConfigs = data.configs;
|
||||
var nodes = data.nodes;
|
||||
initSlidersMaxValuesBasedOnNodeData(nodes);
|
||||
var settings = data.settings;
|
||||
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
||||
$scope.availableConfigs = data.configs;
|
||||
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
|
||||
initSlidersMaxValuesBasedOnNodeData(data.nodes);
|
||||
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.isAdmin = userDetails.role === 1 ? true : false;
|
||||
$scope.isAdmin = userDetails.role === 1;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize view');
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<form class="form-horizontal" autocomplete="off">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="service_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
|
||||
<por-image-registry image="formValues.Image" registry="formValues.Registry" auto-complete="true"></por-image-registry>
|
||||
</div>
|
||||
<!-- !image-and-registry -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
@@ -126,11 +126,11 @@
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<ul class="nav nav-pills nav-justified">
|
||||
<li class="active interactive"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="active interactive"><a data-target="#command" data-toggle="tab">Command & Logging</a></li>
|
||||
<li class="interactive"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="interactive"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
<li class="interactive"><a data-target="#update-config" data-toggle="tab">Update config</a></li>
|
||||
<li class="interactive"><a data-target="#update-config" data-toggle="tab">Update config & Restart</a></li>
|
||||
<li class="interactive" ng-if="applicationState.endpoint.apiVersion >= 1.25"><a data-target="#secrets" data-toggle="tab">Secrets</a></li>
|
||||
<li class="interactive"><a data-target="#configs" data-toggle="tab" ng-if="applicationState.endpoint.apiVersion >= 1.30">Configs</a></li>
|
||||
<li class="interactive"><a data-target="#resources-placement" data-toggle="tab" ng-click="refreshSlider()">Resources & Placement</a></li>
|
||||
@@ -140,6 +140,9 @@
|
||||
<!-- tab-command -->
|
||||
<div class="tab-pane active" id="command">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Command
|
||||
</div>
|
||||
<!-- command-input -->
|
||||
<div class="form-group">
|
||||
<label for="service_command" class="col-sm-2 col-lg-1 control-label text-left">Command</label>
|
||||
@@ -195,6 +198,59 @@
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Logging
|
||||
</div>
|
||||
<!-- logging-driver -->
|
||||
<div class="form-group">
|
||||
<label for="log-driver" class="col-sm-2 col-lg-1 control-label text-left">Driver</label>
|
||||
<div class="col-sm-4">
|
||||
<select class="form-control" ng-model="formValues.LogDriverName" id="log-driver">
|
||||
<option selected value="">Default logging driver</option>
|
||||
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
|
||||
<option value="none">none</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Logging driver for service that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers can be found <a href="https://docs.docker.com/engine/admin/logging/overview/#supported-logging-drivers" target="_blank">in the Docker documentation</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !logging-driver -->
|
||||
<!-- logging-opts -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">
|
||||
Options
|
||||
<portainer-tooltip position="top" message="Add button is disabled unless a driver other than none or default is selected. Options are specific to the selected driver, refer to the driver documentation."></portainer-tooltip>
|
||||
</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="!formValues.LogDriverName || formValues.LogDriverName === 'none' || addLogDriverOpt(formValues.LogDriverName)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add logging driver option
|
||||
</span>
|
||||
</div>
|
||||
<!-- logging-opts-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="opt in formValues.LogDriverOpts" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">option</span>
|
||||
<input type="text" class="form-control" ng-model="opt.name" placeholder="e.g. FOO">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="opt.value" placeholder="e.g. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- logging-opts-input-list -->
|
||||
</div>
|
||||
<!-- !logging-opts -->
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-command -->
|
||||
@@ -308,6 +364,29 @@
|
||||
<!-- !network-input-list -->
|
||||
</div>
|
||||
<!-- !extra-networks -->
|
||||
<!-- extra-hosts-variables -->
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.25">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Hosts file entries</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addHostsEntry()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add additional entry
|
||||
</span>
|
||||
</div>
|
||||
<!-- hosts-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in formValues.HostsEntries" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. host:IP">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeHostsEntry($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hosts-input-list -->
|
||||
</div>
|
||||
<!-- !extra-hosts-variables -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-network -->
|
||||
@@ -372,71 +451,7 @@
|
||||
</div>
|
||||
<!-- !tab-labels -->
|
||||
<!-- tab-update-config -->
|
||||
<div class="tab-pane" id="update-config">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- parallelism-input -->
|
||||
<div class="form-group">
|
||||
<label for="parallelism" class="col-sm-3 col-lg-1 control-label text-left">Parallelism</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="number" class="form-control" ng-model="formValues.Parallelism" id="parallelism" placeholder="e.g. 1">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Maximum number of tasks to be updated simultaneously (0 to update all at once).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !parallelism-input -->
|
||||
<!-- delay-input -->
|
||||
<div class="form-group">
|
||||
<label for="update-delay" class="col-sm-3 col-lg-1 control-label text-left">Delay</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="number" class="form-control" ng-model="formValues.UpdateDelay" id="update-delay" placeholder="e.g. 10">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Amount of time between updates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !delay-input -->
|
||||
<!-- failureAction-input -->
|
||||
<div class="form-group">
|
||||
<label for="failure-action" class="col-sm-3 col-lg-1 control-label text-left">Failure action</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'continue'">Continue</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'pause'">Pause</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Action taken on failure to start after update.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- !failureAction-input -->
|
||||
<!-- order-input -->
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.29">
|
||||
|
||||
<label for="update-order" class="col-sm-3 col-lg-1 control-label text-left">Order</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'start-first'">start-first</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'stop-first'">stop-first</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Operation order on failure.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- !order-input -->
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="update-config" ng-include="'app/components/createService/includes/update-restart.html'"></div>
|
||||
<!-- !tab-update-config -->
|
||||
<!-- tab-secrets -->
|
||||
<div class="tab-pane" id="secrets" ng-if="applicationState.endpoint.apiVersion >= 1.25" ng-include="'app/components/createService/includes/secret.html'"></div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
Memory reservation
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation">
|
||||
@@ -26,7 +26,7 @@
|
||||
Memory limit
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit">
|
||||
@@ -44,7 +44,7 @@
|
||||
CPU reservation
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<por-slider model="formValues.CpuReservation" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
|
||||
<slider model="formValues.CpuReservation" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
|
||||
</div>
|
||||
<div class="col-sm-4" style="margin-top: 20px;">
|
||||
<p class="small text-muted">
|
||||
@@ -59,7 +59,7 @@
|
||||
CPU limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<por-slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
|
||||
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
|
||||
</div>
|
||||
<div class="col-sm-4" style="margin-top: 20px;">
|
||||
<p class="small text-muted">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
Secrets will be available under <code>/run/secrets/$SECRET_NAME</code> in containers.
|
||||
By default, secrets will be available under <code>/run/secrets/$SECRET_NAME</code> in containers.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -12,16 +12,26 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="secret in formValues.Secrets" style="margin-top: 2px;">
|
||||
<div ng-repeat="secret in formValues.Secrets track by $index" style="margin-top: 4px;">
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">secret</span>
|
||||
<select class="form-control" ng-model="secret.model" ng-options="secret.Name for secret in availableSecrets">
|
||||
<option value="" selected="selected">Select a secret</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeSecret($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="input-group col-sm-4 input-group-sm" ng-if="applicationState.endpoint.apiVersion >= 1.30 && secret.overrideTarget">
|
||||
<span class="input-group-addon">target</span>
|
||||
<input class="form-control" ng-model="secret.target" placeholder="/path/in/container">
|
||||
</div>
|
||||
<div class="input-group col-sm-3 input-group-sm">
|
||||
<div class="btn-group btn-group-sm" ng-if="applicationState.endpoint.apiVersion >= 1.30">
|
||||
<label class="btn btn-primary" ng-model="secret.overrideTarget" uib-btn-radio="false">Default location</label>
|
||||
<label class="btn btn-primary" ng-model="secret.overrideTarget" uib-btn-radio="true">Override</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeSecret($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Update config
|
||||
</div>
|
||||
<!-- parallelism-input -->
|
||||
<div class="form-group">
|
||||
<label for="parallelism" class="col-sm-3 col-lg-2 control-label text-left">Update parallelism</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="number" class="form-control" ng-model="formValues.Parallelism" id="parallelism" placeholder="e.g. 1">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Maximum number of tasks to be updated simultaneously (0 to update all at once).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !parallelism-input -->
|
||||
<!-- delay-input -->
|
||||
<div class="form-group">
|
||||
<label for="update-delay" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Update delay
|
||||
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="text" class="form-control" ng-model="formValues.UpdateDelay" id="update-delay" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0s, 0 seconds.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !delay-input -->
|
||||
<!-- failureAction-input -->
|
||||
<div class="form-group">
|
||||
<label for="failure-action" class="col-sm-3 col-lg-2 control-label text-left">Update failure action</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'continue'">Continue</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'pause'">Pause</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Action taken on failure to start after update.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !failureAction-input -->
|
||||
<!-- order-input -->
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.29">
|
||||
<label for="update-order" class="col-sm-3 col-lg-2 control-label text-left">Update order</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'start-first'">start-first</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'stop-first'">stop-first</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Operation order on failure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !order-input -->
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Restart policy
|
||||
</div>
|
||||
<!-- restartCondition-input -->
|
||||
<div class="form-group">
|
||||
<label for="restart-condition" class="col-sm-3 col-lg-2 control-label text-left">Restart condition</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'none'">None</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'on-failure'">On-failure</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'any'">Any</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Restart when condition is met (default condition "any").
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restartCondition-input -->
|
||||
<!-- restartDelay-input -->
|
||||
<div class="form-group">
|
||||
<label for="restart-delay" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Restart delay
|
||||
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="text" class="form-control" ng-model="formValues.RestartDelay" id="restart-delay" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restartDelay-input -->
|
||||
<!-- restartMaxAttempts-input -->
|
||||
<div class="form-group">
|
||||
<label for="restart-max-attempts" class="col-sm-3 col-lg-2 control-label text-left">Restart max attempts</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="number" class="form-control" ng-model="formValues.RestartMaxAttempts" id="restart-max-attempts" placeholder="e.g. 0">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restartMaxAttempts-input -->
|
||||
<!-- restartWindow-input -->
|
||||
<div class="form-group">
|
||||
<label for="restart-window" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Restart window
|
||||
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-4 col-lg-3">
|
||||
<input type="text" class="form-control" ng-model="formValues.RestartWindow" id="restart-window" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<p class="small text-muted">
|
||||
Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restartWindow-input -->
|
||||
</form>
|
||||
@@ -67,7 +67,7 @@ function ($scope, $state, $document, StackService, CodeMirrorService, Authentica
|
||||
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
var userId = userDetails.ID;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('createVolume', [])
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator) {
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator', 'ExtensionManager',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator, ExtensionManager) {
|
||||
|
||||
$scope.formValues = {
|
||||
Driver: 'local',
|
||||
@@ -40,10 +40,16 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
|
||||
var name = $scope.formValues.Name;
|
||||
var driver = $scope.formValues.Driver;
|
||||
var driverOptions = $scope.formValues.DriverOptions;
|
||||
var storidgeProfile = $scope.formValues.StoridgeProfile;
|
||||
|
||||
if (driver === 'cio:latest' && storidgeProfile) {
|
||||
driverOptions.push({ name: 'profile', value: storidgeProfile.Name });
|
||||
}
|
||||
|
||||
var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions);
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
@@ -82,5 +88,11 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
|
||||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
ExtensionManager.init()
|
||||
.then(function success(data) {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize extensions');
|
||||
});
|
||||
}]);
|
||||
|
||||
@@ -62,6 +62,14 @@
|
||||
<!-- !driver-options-input-list -->
|
||||
</div>
|
||||
<!-- !driver-options -->
|
||||
<!-- storidge -->
|
||||
<div ng-if="formValues.Driver === 'cio:latest'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Storidge
|
||||
</div>
|
||||
<storidge-profile-selector storidge-profile="formValues.StoridgeProfile"></storidge-profile-selector>
|
||||
</div>
|
||||
<!-- storidge -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
|
||||
@@ -75,6 +75,13 @@
|
||||
<td>Nodes in the cluster</td>
|
||||
<td>{{ infoData.Swarm.Nodes }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
|
||||
@@ -67,93 +67,26 @@
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.Name || !formValues.URL || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (formValues.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))" ng-click="addEndpoint()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title="Endpoints">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left" ng-if="applicationState.application.endpointManagement">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-if="applicationState.application.endpointManagement">
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="endpoints" ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="endpoints" ng-click="order('URL')">
|
||||
URL
|
||||
<span ng-show="sortType == 'URL' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'URL' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="endpoint in (state.filteredEndpoints = (endpoints | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td ng-if="applicationState.application.endpointManagement"><input type="checkbox" ng-model="endpoint.Checked" ng-change="selectItem(endpoint)" /></td>
|
||||
<td><i class="fa fa-star" aria-hidden="true" ng-if="endpoint.Id === activeEndpoint.Id"></i> {{ endpoint.Name }}</td>
|
||||
<td>{{ endpoint.URL | stripprotocol }}</td>
|
||||
<td>
|
||||
<span ng-if="applicationState.application.endpointManagement">
|
||||
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</span>
|
||||
<span ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})"><i class="fa fa-users" aria-hidden="true" style="margin-left: 7px;"></i> Manage access</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!endpoints">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="endpoints.length == 0">
|
||||
<td colspan="5" class="text-center text-muted">No endpoints available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="endpoints" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<endpoints-datatable
|
||||
title="Endpoints" title-icon="fa-plug"
|
||||
dataset="endpoints" table-key="endpoints"
|
||||
order-by="Name" show-text-filter="true"
|
||||
endpoint-management="applicationState.application.endpointManagement"
|
||||
access-management="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
></endpoints-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
angular.module('endpoints', [])
|
||||
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'EndpointProvider', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, $filter, EndpointService, EndpointProvider, Notifications, Pagination) {
|
||||
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications',
|
||||
function ($scope, $state, $filter, EndpointService, Notifications) {
|
||||
$scope.state = {
|
||||
uploadInProgress: false,
|
||||
selectedItemCount: 0,
|
||||
pagination_count: Pagination.getPaginationCount('endpoints'),
|
||||
actionInProgress: false
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
@@ -17,32 +13,6 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati
|
||||
SecurityFormData: new EndpointSecurityFormData()
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredEndpoints, function (endpoint) {
|
||||
if (endpoint.Checked !== allSelected) {
|
||||
endpoint.Checked = allSelected;
|
||||
$scope.selectItem(endpoint);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||
@@ -75,17 +45,24 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
angular.forEach($scope.endpoints, function (endpoint) {
|
||||
if (endpoint.Checked) {
|
||||
EndpointService.deleteEndpoint(endpoint.Id).then(function success(data) {
|
||||
Notifications.success('Endpoint deleted', endpoint.Name);
|
||||
var index = $scope.endpoints.indexOf(endpoint);
|
||||
$scope.endpoints.splice(index, 1);
|
||||
}, function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove endpoint');
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (endpoint) {
|
||||
EndpointService.deleteEndpoint(endpoint.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Endpoint successfully removed', endpoint.Name);
|
||||
var index = $scope.endpoints.indexOf(endpoint);
|
||||
$scope.endpoints.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove endpoint');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -8,66 +8,12 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-history" title="Events">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="events" ng-click="order('Time')">
|
||||
Date
|
||||
<span ng-show="sortType == 'Time' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Time' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="events" ng-click="order('Type')">
|
||||
Category
|
||||
<span ng-show="sortType == 'Type' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Type' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="events" ng-click="order('Details')">
|
||||
Details
|
||||
<span ng-show="sortType == 'Details' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Details' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="event in (events | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
|
||||
<td>{{ event.Time|getisodatefromtimestamp }}</td>
|
||||
<td>{{ event.Type }}</td>
|
||||
<td>{{ event.Details }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="events" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<events-datatable
|
||||
title="Events" title-icon="fa-history"
|
||||
dataset="events" table-key="events"
|
||||
order-by="Time" reverse-order="true"
|
||||
show-text-filter="true"
|
||||
></events-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
angular.module('events', [])
|
||||
.controller('EventsController', ['$scope', 'Notifications', 'SystemService', 'Pagination',
|
||||
function ($scope, Notifications, SystemService, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('events');
|
||||
$scope.sortType = 'Time';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('events', $scope.state.pagination_count);
|
||||
};
|
||||
.controller('EventsController', ['$scope', 'Notifications', 'SystemService',
|
||||
function ($scope, Notifications, SystemService) {
|
||||
|
||||
function initView() {
|
||||
var from = moment().subtract(24, 'hour').unix();
|
||||
|
||||
@@ -41,112 +41,13 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-clone" title="Images">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="!state.selectedItemCount" >
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="confirmRemovalAction(true)" ng-disabled="!state.selectedItemCount" >Force Remove</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
<span class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
|
||||
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="undefined">
|
||||
All
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="'!' + 0">
|
||||
Used
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="0">
|
||||
Unused
|
||||
</label>
|
||||
</span>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" ng-click="order('Id')">
|
||||
Id
|
||||
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" ng-click="order('RepoTags')">
|
||||
Tags
|
||||
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" ng-click="order('VirtualSize')">
|
||||
Size
|
||||
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" ng-click="order('Created')">
|
||||
Created
|
||||
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="image in (state.filteredImages = (images | filter:{ ContainerCount: state.containersCountFilter } | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
|
||||
<td>
|
||||
<a class="monospaced" ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a>
|
||||
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="::image.ContainerCount === 0">Unused</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-primary image-tag" ng-repeat="tag in (image|repotags)">{{ tag }}</span>
|
||||
</td>
|
||||
<td>{{ image.VirtualSize|humansize }}</td>
|
||||
<td>{{ image.Created|getisodatefromtimestamp }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!images">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="state.filteredImages.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No images available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="images" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<images-datatable
|
||||
title="Images" title-icon="fa-clone"
|
||||
dataset="images" table-key="images"
|
||||
order-by="RepoTags" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
force-remove-action="confirmRemovalAction"
|
||||
></images-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +1,15 @@
|
||||
angular.module('images', [])
|
||||
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'Pagination', 'ModalService',
|
||||
function ($scope, $state, ImageService, Notifications, Pagination, ModalService) {
|
||||
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService',
|
||||
function ($scope, $state, ImageService, Notifications, ModalService) {
|
||||
$scope.state = {
|
||||
pagination_count: Pagination.getPaginationCount('images'),
|
||||
actionInProgress: false,
|
||||
selectedItemCount: 0
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
$scope.sortType = 'RepoTags';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.formValues = {
|
||||
Image: '',
|
||||
Registry: ''
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('images', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredImages, function (image) {
|
||||
if (image.Checked !== allSelected) {
|
||||
image.Checked = allSelected;
|
||||
$scope.selectItem(image);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.pullImage = function() {
|
||||
var image = $scope.formValues.Image;
|
||||
var registry = $scope.formValues.Registry;
|
||||
@@ -59,33 +28,38 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
|
||||
});
|
||||
};
|
||||
|
||||
$scope.confirmRemovalAction = function (force) {
|
||||
$scope.confirmRemovalAction = function (selectedItems, force) {
|
||||
ModalService.confirmImageForceRemoval(function (confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
$scope.removeAction(force);
|
||||
$scope.removeAction(selectedItems, force);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function (force) {
|
||||
force = !!force;
|
||||
angular.forEach($scope.images, function (i) {
|
||||
if (i.Checked) {
|
||||
ImageService.deleteImage(i.Id, force)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Image deleted', i.Id);
|
||||
var index = $scope.images.indexOf(i);
|
||||
$scope.images.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove image');
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function (selectedItems, force) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (image) {
|
||||
ImageService.deleteImage(image.Id, force)
|
||||
.then(function success() {
|
||||
Notifications.success('Image successfully removed', image.Id);
|
||||
var index = $scope.images.indexOf(image);
|
||||
$scope.images.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove image');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function fetchImages() {
|
||||
function initView() {
|
||||
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
ImageService.images(true)
|
||||
.then(function success(data) {
|
||||
$scope.images = data;
|
||||
@@ -96,5 +70,5 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
|
||||
});
|
||||
}
|
||||
|
||||
fetchImages();
|
||||
initView();
|
||||
}]);
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<networks-datatable
|
||||
title="Networks" title-icon="fa-sitemap"
|
||||
dataset="networks" table-key="networks"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
></networks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-sitemap" title="Networks">
|
||||
@@ -98,7 +110,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<tr dir-paginate="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: network.Checked}">
|
||||
<td><input type="checkbox" ng-model="network.Checked" ng-change="selectItem(network)"/></td>
|
||||
<td><a ui-sref="network({id: network.Id})">{{ network.Name | truncate:40 }}</a></td>
|
||||
<td>{{ network.StackName ? network.StackName : '-' }}</td>
|
||||
@@ -129,4 +141,4 @@
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
@@ -1,54 +1,25 @@
|
||||
angular.module('networks', [])
|
||||
.controller('NetworksController', ['$scope', '$state', 'Network', 'NetworkService', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, Network, NetworkService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('networks');
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.advancedSettings = false;
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
.controller('NetworksController', ['$scope', '$state', 'NetworkService', 'Notifications',
|
||||
function ($scope, $state, NetworkService, Notifications) {
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('networks', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItems = function(allSelected) {
|
||||
angular.forEach($scope.state.filteredNetworks, function (network) {
|
||||
if (network.Checked !== allSelected) {
|
||||
network.Checked = allSelected;
|
||||
$scope.selectItem(network);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
angular.forEach($scope.networks, function (network) {
|
||||
if (network.Checked) {
|
||||
Network.remove({id: network.Id}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.error('Error', d, 'Unable to remove network');
|
||||
} else {
|
||||
Notifications.success('Network removed', network.Id);
|
||||
var index = $scope.networks.indexOf(network);
|
||||
$scope.networks.splice(index, 1);
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to remove network');
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (network) {
|
||||
NetworkService.remove(network.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Network successfully removed', network.Name);
|
||||
var index = $scope.networks.indexOf(network);
|
||||
$scope.networks.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove network');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -143,6 +143,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="node">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-object-group" title="Engine labels"></rd-widget-header>
|
||||
<rd-widget-body ng-if="!node.EngineLabels || node.EngineLabels.length === 0">
|
||||
<p>There are no engine labels for this node.</p>
|
||||
</rd-widget-body>
|
||||
<rd-widget-body classes="no-padding" ng-if="node.EngineLabels && node.EngineLabels.length > 0">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="engineLabel in node.EngineLabels">
|
||||
<td>{{ engineLabel.key }}</td>
|
||||
<td>{{ engineLabel.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="node">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<rd-widget>
|
||||
@@ -205,69 +232,12 @@
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="node && tasks.length > 0">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Associated tasks">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>
|
||||
<a ui-sref="node" ng-click="order('Status')">
|
||||
Status
|
||||
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="node" ng-click="order('Slot')">
|
||||
Slot
|
||||
<span ng-show="sortType == 'Slot' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Slot' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="node" ng-click="order('Spec.ContainerSpec.Image')">
|
||||
Image
|
||||
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="node" ng-click="order('Updated')">
|
||||
Last update
|
||||
<span ng-show="sortType == 'Updated' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Updated' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
|
||||
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
|
||||
<td>{{ task.Slot ? task.Slot : '-' }}</td>
|
||||
<td>{{ task.Spec.ContainerSpec.Image | hideshasum }}</td>
|
||||
<td>{{ task.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="tasks" class="pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<node-tasks-datatable
|
||||
title="Tasks" title-icon="fa-tasks"
|
||||
dataset="tasks" table-key="node-tasks"
|
||||
order-by="Updated" reverse-order="true"
|
||||
show-text-filter="true"
|
||||
></node-tasks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||
// See app/components/templates/templatesController.js as a reference.
|
||||
angular.module('node', [])
|
||||
.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Pagination', 'Notifications',
|
||||
function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Pagination, Notifications) {
|
||||
.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Notifications',
|
||||
function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Notifications) {
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('node_tasks');
|
||||
$scope.loading = true;
|
||||
$scope.tasks = [];
|
||||
$scope.sortType = 'Status';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
var originalNode = {};
|
||||
var editedKeys = [];
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('node_tasks', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.updateNodeAttribute = function updateNodeAttribute(node, key) {
|
||||
editedKeys.push(key);
|
||||
};
|
||||
|
||||
@@ -70,82 +70,12 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-database" title="Available registries">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.registry"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="registries" ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="registries" ng-click="order('URL')">
|
||||
URL
|
||||
<span ng-show="sortType == 'URL' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'URL' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="registry in (state.filteredRegistries = (registries | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="registry.Checked" ng-change="selectItem(registry)" /></td>
|
||||
<td>
|
||||
<a ui-sref="registry({id: registry.Id})">{{ registry.Name }}</a>
|
||||
<span ng-if="registry.Authentication" style="margin-left: 5px;">
|
||||
<i class="fa fa-shield" aria-hidden="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Authentication is enabled for this registry."></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ registry.URL }}</td>
|
||||
<td>
|
||||
<span ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="registry.access({id: registry.Id})"><i class="fa fa-users" aria-hidden="true" style="margin-left: 7px;"></i> Manage access</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!registries">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="registries.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No registries available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="registries" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<registries-datatable
|
||||
title="Registries" title-icon="fa-database"
|
||||
dataset="registries" table-key="registries"
|
||||
order-by="Name" show-text-filter="true"
|
||||
access-management="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
></registries-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
angular.module('registries', [])
|
||||
.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'Pagination',
|
||||
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, Pagination) {
|
||||
.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'PaginationService',
|
||||
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, PaginationService) {
|
||||
|
||||
$scope.state = {
|
||||
selectedItemCount: 0,
|
||||
pagination_count: Pagination.getPaginationCount('registries'),
|
||||
actionInProgress: false
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.updateDockerHub = function() {
|
||||
var dockerhub = $scope.dockerhub;
|
||||
@@ -25,56 +21,34 @@ function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, N
|
||||
});
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredRegistries, function (registry) {
|
||||
if (registry.Checked !== allSelected) {
|
||||
registry.Checked = allSelected;
|
||||
$scope.selectItem(registry);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function() {
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected registries?',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeRegistries();
|
||||
deleteSelectedRegistries(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function removeRegistries() {
|
||||
var registries = $scope.registries;
|
||||
angular.forEach(registries, function (registry) {
|
||||
if (registry.Checked) {
|
||||
RegistryService.deleteRegistry(registry.Id)
|
||||
.then(function success(data) {
|
||||
var index = registries.indexOf(registry);
|
||||
registries.splice(index, 1);
|
||||
Notifications.success('Registry deleted', registry.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove registry');
|
||||
});
|
||||
}
|
||||
function deleteSelectedRegistries(selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (registry) {
|
||||
RegistryService.deleteRegistry(registry.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Registry successfully removed', registry.Name);
|
||||
var index = $scope.registries.indexOf(registry);
|
||||
$scope.registries.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove registry');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,73 +8,13 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-user-secret" title="Secrets">
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.secret"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('CreatedAt')">
|
||||
Created at
|
||||
<span ng-show="sortType == 'CreatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'CreatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="secret in (state.filteredSecrets = ( secrets | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))">
|
||||
<td><input type="checkbox" ng-model="secret.Checked" ng-change="selectItem(secret)"/></td>
|
||||
<td><a ui-sref="secret({id: secret.Id})">{{ secret.Name }}</a></td>
|
||||
<td>{{ secret.CreatedAt | getisodate }}</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="secret.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ secret.ResourceControl.Ownership ? secret.ResourceControl.Ownership : secret.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!secrets">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="secrets.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No secrets available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="secrets" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<secrets-datatable
|
||||
title="Secrets" title-icon="fa-user-secret"
|
||||
dataset="secrets" table-key="secrets"
|
||||
order-by="Name" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
></secrets-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,25 @@
|
||||
angular.module('secrets', [])
|
||||
.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, SecretService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('secrets');
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications',
|
||||
function ($scope, $state, SecretService, Notifications) {
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredSecrets, function (secret) {
|
||||
if (secret.Checked !== allSelected) {
|
||||
secret.Checked = allSelected;
|
||||
$scope.selectItem(secret);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
angular.forEach($scope.secrets, function (secret) {
|
||||
if (secret.Checked) {
|
||||
SecretService.remove(secret.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Secret deleted', secret.Id);
|
||||
var index = $scope.secrets.indexOf(secret);
|
||||
$scope.secrets.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove secret');
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (secret) {
|
||||
SecretService.remove(secret.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Secret successfully removed', secret.Name);
|
||||
var index = $scope.secrets.indexOf(secret);
|
||||
$scope.secrets.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove secret');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<div>
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Hosts file entries">
|
||||
<div class="nopadding">
|
||||
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addHostsEntry(service)" ng-disabled="isUpdating">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add host entry
|
||||
</a>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body ng-if="!service.Hosts || service.Hosts.length === 0">
|
||||
<p>The Hosts file has no extra entries.</p>
|
||||
</rd-widget-body>
|
||||
<rd-widget-body ng-if="service.Hosts.length > 0" classes="no-padding">
|
||||
<table class="table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="entry in service.Hosts">
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" ng-model="entry.hostname" placeholder="e.g. example.com" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" ng-model="entry.ip" placeholder="e.g. 10.0.1.1" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeHostsEntry(service, $index)" ng-disabled="isUpdating">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
<rd-widget-footer>
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['Hosts'])" ng-click="updateService(service)">Apply changes</button>
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="cancelChanges(service, ['Hosts'])">Reset changes</a></li>
|
||||
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-footer>
|
||||
</rd-widget>
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
<div id="service-logging-driver">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Logging driver">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="form-inline" style="padding: 10px;">
|
||||
Driver:
|
||||
<select class="form-control" ng-model="service.LogDriverName" ng-change="updateLogDriverName(service)" ng-disabled="isUpdating">
|
||||
<option selected value="">Default logging driver</option>
|
||||
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
|
||||
<option value="none">none</option>
|
||||
</select>
|
||||
<a class="btn btn-default btn-sm" ng-click="!service.LogDriverName || service.LogDriverName === 'none' || addLogDriverOpt(service)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add logging driver option
|
||||
</a>
|
||||
</div>
|
||||
<table class="table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="option in service.LogDriverOpts">
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">name</span>
|
||||
<input type="text" class="form-control" ng-model="option.key" ng-disabled="option.added || isUpdating" placeholder="e.g. FOO">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">value</span>
|
||||
<input type="text" class="form-control" ng-model="option.value" ng-change="updateLogDriverOpt(service, option)" placeholder="e.g. bar" ng-disabled="isUpdating">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt(service, $index)" ng-disabled="isUpdating">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="service.LogDriverOpts.length === 0">
|
||||
<td colspan="6" class="text-center text-muted">No options associated to this logging driver.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
<rd-widget-footer>
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['LogDriverName', 'LogDriverOpts'])" ng-click="updateService(service)">Apply changes</button>
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="cancelChanges(service, ['LogDriverName', 'LogDriverOpts'])">Reset changes</a></li>
|
||||
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-footer>
|
||||
</rd-widget>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<por-slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')"></por-slider>
|
||||
<slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')"></slider>
|
||||
</td>
|
||||
<td style="vertical-align : middle;">
|
||||
<p class="small text-muted">
|
||||
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<por-slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')"></por-slider>
|
||||
<slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')"></slider>
|
||||
</td>
|
||||
<td style="vertical-align : middle;">
|
||||
<p class="small text-muted">
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
<tr>
|
||||
<td>Restart delay</td>
|
||||
<td>
|
||||
<input class="input-sm" type="number" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-disabled="isUpdating"/>
|
||||
<input class="input-sm" type="text" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
|
||||
</td>
|
||||
<td>
|
||||
<p class="small text-muted" style="margin-top: 10px;">
|
||||
Delay between restart attempts. Time in seconds.
|
||||
Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -40,18 +40,18 @@
|
||||
</td>
|
||||
<td>
|
||||
<p class="small text-muted" style="margin-top: 10px;">
|
||||
Maximum attempts to restart a given container before giving up (default value is 0, which is ignored).
|
||||
Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited).
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Restart window</td>
|
||||
<td>
|
||||
<input class="input-sm" type="number" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-disabled="isUpdating"/>
|
||||
<input class="input-sm" type="text" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
|
||||
</td>
|
||||
<td>
|
||||
<p class="small text-muted" style="margin-top: 10px;">
|
||||
The time window used to evaluate the restart policy (default value is 0, which is unbounded).
|
||||
Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="form-inline" style="padding: 10px;">
|
||||
Add a secret:
|
||||
<select class="form-control" ng-options="secret.Name for secret in secrets" ng-model="newSecret">
|
||||
<select class="form-control" ng-options="secret.Name for secret in secrets" ng-model="state.addSecret.secret">
|
||||
<option selected disabled hidden value="">Select a secret</option>
|
||||
</select>
|
||||
<a class="btn btn-default btn-sm" ng-click="addSecret(service, newSecret)">
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.30 && state.addSecret.override">
|
||||
Target:
|
||||
<input class="form-control" ng-model="state.addSecret.target" placeholder="/path/in/container">
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" ng-if="applicationState.endpoint.apiVersion >= 1.30">
|
||||
<label class="btn btn-primary" ng-model="state.addSecret.override" uib-btn-radio="false">Default location</label>
|
||||
<label class="btn btn-primary" ng-model="state.addSecret.override" uib-btn-radio="true">Override</label>
|
||||
</div>
|
||||
<a class="btn btn-default btn-sm" ng-click="addSecret(service, state.addSecret)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add secret
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,71 +1,10 @@
|
||||
<div ng-if="tasks.length > 0 && nodes" id="service-tasks">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Associated tasks">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>
|
||||
<a ng-click="order('Status.State')">
|
||||
Status
|
||||
<span ng-show="sortType == 'Status.State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status.State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="service.Mode !== 'global'">
|
||||
<a ng-click="order('Slot')">
|
||||
Slot
|
||||
<span ng-show="sortType == 'Slot' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Slot' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('NodeId')">
|
||||
Node
|
||||
<span ng-show="sortType == 'NodeId' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'NodeId' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Updated')">
|
||||
Last update
|
||||
<span ng-show="sortType == 'Updated' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Updated' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="task in (filteredTasks = ( tasks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><a ui-sref="task({ id: task.Id })" class="monospaced">{{ task.Id }}</a></td>
|
||||
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
|
||||
<td ng-if="service.Mode !== 'global'">{{ task.Slot }}</td>
|
||||
<td>{{ task.NodeId | tasknodename: nodes }}</td>
|
||||
<td>{{ task.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="tasks" class="pagination-controls" >
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<tasks-datatable
|
||||
title="Tasks" title-icon="fa-tasks"
|
||||
dataset="tasks" table-key="service-tasks"
|
||||
order-by="Updated" reverse-order="true"
|
||||
nodes="nodes"
|
||||
show-text-filter="true"
|
||||
show-slot-column="service.Mode !== 'global'"
|
||||
></tasks-datatable>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<tr>
|
||||
<td>Update Delay</td>
|
||||
<td>
|
||||
<input class="input-sm" type="number" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-disabled="isUpdating"/>
|
||||
<input class="input-sm" type="text" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
|
||||
</td>
|
||||
<td>
|
||||
<p class="small text-muted" style="margin-top: 10px;">
|
||||
Amount of time between updates.
|
||||
Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Example: 1m.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
<td>ID</td>
|
||||
<td>
|
||||
{{ service.Id }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeService()"><i class="fa fa-trash space-right" aria-hidden="true" ng-disabled="isUpdating"></i>Delete this service</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="service.CreatedAt">
|
||||
@@ -68,14 +67,21 @@
|
||||
<tr>
|
||||
<td>Image</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" ng-disabled="isUpdating" />
|
||||
<input type="text" class="form-control" uib-typeahead="image for image in availableImages | filter:$viewValue | limitTo:5"
|
||||
ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" id="image_name" ng-disabled="isUpdating">
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="servicelogs({id: service.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
</div>
|
||||
<a ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="servicelogs({id: service.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Service logs</a>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
|
||||
<span ng-hide="state.updateInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Update the service</span>
|
||||
<span ng-show="state.updateInProgress">Update in progress...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress">
|
||||
<span ng-hide="state.deletionInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete the service</span>
|
||||
<span ng-show="state.deletionInProgress">Deletion in progress...</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -115,6 +121,7 @@
|
||||
<li ng-if="applicationState.endpoint.apiVersion >= 1.30"><a href ng-click="goToItem('service-placement-preferences')">Placement preferences</a></li>
|
||||
<li><a href ng-click="goToItem('service-restart-policy')">Restart policy</a></li>
|
||||
<li><a href ng-click="goToItem('service-update-config')">Update configuration</a></li>
|
||||
<li><a href ng-click="goToItem('service-logging')">Logging</a></li>
|
||||
<li><a href ng-click="goToItem('service-labels')">Service labels</a></li>
|
||||
<li><a href ng-click="goToItem('service-configs')">Configs</a></li>
|
||||
<li ng-if="applicationState.endpoint.apiVersion >= 1.25"><a href ng-click="goToItem('service-secrets')">Secrets</a></li>
|
||||
@@ -151,6 +158,7 @@
|
||||
<h3 id="service-network-specs">Networks & ports</h3>
|
||||
<div id="service-networks" class="padding-top" ng-include="'app/components/service/includes/networks.html'"></div>
|
||||
<div id="service-published-ports" class="padding-top" ng-include="'app/components/service/includes/ports.html'"></div>
|
||||
<div id="service-hosts-entries" class="padding-top" ng-include="'app/components/service/includes/hosts.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -163,6 +171,7 @@
|
||||
<div id="service-placement-preferences" ng-if="applicationState.endpoint.apiVersion >= 1.30" class="padding-top" ng-include="'app/components/service/includes/placementPreferences.html'"></div>
|
||||
<div id="service-restart-policy" class="padding-top" ng-include="'app/components/service/includes/restart.html'"></div>
|
||||
<div id="service-update-config" class="padding-top" ng-include="'app/components/service/includes/updateconfig.html'"></div>
|
||||
<div id="service-logging" class="padding-top" ng-include="'app/components/service/includes/logging.html'"></div>
|
||||
<div id="service-labels" class="padding-top" ng-include="'app/components/service/includes/servicelabels.html'"></div>
|
||||
<div id="service-configs" class="padding-top" ng-include="'app/components/service/includes/configs.html'"></div>
|
||||
<div id="service-secrets" ng-if="applicationState.endpoint.apiVersion >= 1.25" class="padding-top" ng-include="'app/components/service/includes/secrets.html'"></div>
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
angular.module('service', [])
|
||||
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService',
|
||||
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService) {
|
||||
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'ModalService', 'PluginService',
|
||||
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, ModalService, PluginService) {
|
||||
|
||||
$scope.state = {
|
||||
updateInProgress: false,
|
||||
deletionInProgress: false
|
||||
};
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('service_tasks');
|
||||
$scope.tasks = [];
|
||||
$scope.sortType = 'Updated';
|
||||
$scope.sortReverse = true;
|
||||
$scope.availableImages = [];
|
||||
|
||||
$scope.lastVersion = 0;
|
||||
|
||||
var originalService = {};
|
||||
var previousServiceValues = [];
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('service_tasks', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.renameService = function renameService(service) {
|
||||
updateServiceAttribute(service, 'Name', service.newServiceName || service.name);
|
||||
service.EditName = false;
|
||||
@@ -74,10 +67,16 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
$scope.updateConfig = function updateConfig(service) {
|
||||
updateServiceArray(service, 'ServiceConfigs', service.ServiceConfigs);
|
||||
};
|
||||
$scope.addSecret = function addSecret(service, secret) {
|
||||
if (secret && service.ServiceSecrets.filter(function(serviceSecret) { return serviceSecret.Id === secret.Id;}).length === 0) {
|
||||
service.ServiceSecrets.push({ Id: secret.Id, Name: secret.Name, FileName: secret.Name, Uid: '0', Gid: '0', Mode: 444 });
|
||||
updateServiceArray(service, 'ServiceSecrets', service.ServiceSecrets);
|
||||
$scope.addSecret = function addSecret(service, newSecret) {
|
||||
if (newSecret.secret) {
|
||||
var filename = newSecret.secret.Name;
|
||||
if (newSecret.override) {
|
||||
filename = newSecret.target;
|
||||
}
|
||||
if (service.ServiceSecrets.filter(function(serviceSecret) { return serviceSecret.Id === newSecret.secret.Id && serviceSecret.FileName === filename;}).length === 0) {
|
||||
service.ServiceSecrets.push({ Id: newSecret.secret.Id, Name: newSecret.secret.Name, FileName: filename, Uid: '0', Gid: '0', Mode: 444 });
|
||||
updateServiceArray(service, 'ServiceSecrets', service.ServiceSecrets);
|
||||
}
|
||||
}
|
||||
};
|
||||
$scope.removeSecret = function removeSecret(service, index) {
|
||||
@@ -173,6 +172,41 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addLogDriverOpt = function addLogDriverOpt(service) {
|
||||
service.LogDriverOpts.push({ key: '', value: '', originalValue: '' });
|
||||
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
||||
};
|
||||
$scope.removeLogDriverOpt = function removeLogDriverOpt(service, index) {
|
||||
var removedElement = service.LogDriverOpts.splice(index, 1);
|
||||
if (removedElement !== null) {
|
||||
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
||||
}
|
||||
};
|
||||
$scope.updateLogDriverOpt = function updateLogDriverOpt(service, variable) {
|
||||
if (variable.value !== variable.originalValue || variable.key !== variable.originalKey) {
|
||||
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
||||
}
|
||||
};
|
||||
$scope.updateLogDriverName = function updateLogDriverName(service) {
|
||||
updateServiceArray(service, 'LogDriverName', service.LogDriverName);
|
||||
};
|
||||
|
||||
$scope.addHostsEntry = function (service) {
|
||||
if (!service.Hosts) {
|
||||
service.Hosts = [];
|
||||
}
|
||||
service.Hosts.push({ hostname: '', ip: '' });
|
||||
};
|
||||
$scope.removeHostsEntry = function(service, index) {
|
||||
var removedElement = service.Hosts.splice(index, 1);
|
||||
if (removedElement !== null) {
|
||||
updateServiceArray(service, 'Hosts', service.Hosts);
|
||||
}
|
||||
};
|
||||
$scope.updateHostsEntry = function(service, entry) {
|
||||
updateServiceArray(service, 'Hosts', service.Hosts);
|
||||
};
|
||||
|
||||
$scope.cancelChanges = function cancelChanges(service, keys) {
|
||||
if (keys) { // clean out the keys only from the list of modified keys
|
||||
keys.forEach(function(key) {
|
||||
@@ -208,6 +242,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
config.TaskTemplate.ContainerSpec.Image = service.Image;
|
||||
config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : [];
|
||||
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
|
||||
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
|
||||
|
||||
if (service.Mode === 'replicated') {
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
@@ -237,17 +272,30 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
|
||||
config.UpdateConfig = {
|
||||
Parallelism: service.UpdateParallelism,
|
||||
Delay: service.UpdateDelay,
|
||||
Delay: ServiceHelper.translateHumanDurationToNanos(service.UpdateDelay) || 0,
|
||||
FailureAction: service.UpdateFailureAction,
|
||||
Order: service.UpdateOrder
|
||||
};
|
||||
|
||||
config.TaskTemplate.RestartPolicy = {
|
||||
Condition: service.RestartCondition,
|
||||
Delay: service.RestartDelay,
|
||||
MaxAttempts: service.RestartMaxAttempts,
|
||||
Window: service.RestartWindow
|
||||
};
|
||||
if ($scope.hasChanges(service, ['RestartCondition', 'RestartDelay', 'RestartMaxAttempts', 'RestartWindow'])){
|
||||
config.TaskTemplate.RestartPolicy = {
|
||||
Condition: service.RestartCondition,
|
||||
Delay: ServiceHelper.translateHumanDurationToNanos(service.RestartDelay) || 5000000000,
|
||||
MaxAttempts: service.RestartMaxAttempts,
|
||||
Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0
|
||||
};
|
||||
}
|
||||
|
||||
config.TaskTemplate.LogDriver = null;
|
||||
if (service.LogDriverName) {
|
||||
config.TaskTemplate.LogDriver = { Name: service.LogDriverName };
|
||||
if (service.LogDriverName !== 'none') {
|
||||
var logOpts = ServiceHelper.translateKeyValueToLogDriverOpts(service.LogDriverOpts);
|
||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||
config.TaskTemplate.LogDriver.Options = logOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (service.Ports) {
|
||||
service.Ports.forEach(function (binding) {
|
||||
@@ -258,7 +306,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
}
|
||||
|
||||
config.EndpointSpec = {
|
||||
Mode: config.EndpointSpec.Mode || 'vip',
|
||||
Mode: (config.EndpointSpec && config.EndpointSpec.Mode) || 'vip',
|
||||
Ports: service.Ports
|
||||
};
|
||||
|
||||
@@ -286,6 +334,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
};
|
||||
|
||||
function removeService() {
|
||||
$scope.state.deletionInProgress = true;
|
||||
ServiceService.remove($scope.service)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully deleted');
|
||||
@@ -293,6 +342,39 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.deletionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.forceUpdateService = function(service) {
|
||||
ModalService.confirmServiceForceUpdate(
|
||||
'Do you want to force update this service? All the tasks associated to the selected service(s) will be recreated.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
forceUpdateService(service);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function forceUpdateService(service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
// As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
|
||||
// value or an increment of the counter value to force an update.
|
||||
config.TaskTemplate.ForceUpdate++;
|
||||
$scope.state.updateInProgress = true;
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully updated', service.Name);
|
||||
$scope.cancelChanges({});
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to force update service', service.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.updateInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,11 +382,13 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
||||
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
|
||||
service.EnvironmentVariables = ServiceHelper.translateEnvironmentVariables(service.Env);
|
||||
service.LogDriverOpts = ServiceHelper.translateLogDriverOptsToKeyValue(service.LogDriverOpts);
|
||||
service.ServiceLabels = LabelHelper.fromLabelHashToKeyValue(service.Labels);
|
||||
service.ServiceContainerLabels = LabelHelper.fromLabelHashToKeyValue(service.ContainerLabels);
|
||||
service.ServiceMounts = angular.copy(service.Mounts);
|
||||
service.ServiceConstraints = ServiceHelper.translateConstraintsToKeyValue(service.Constraints);
|
||||
service.ServicePreferences = ServiceHelper.translatePreferencesToKeyValue(service.Preferences);
|
||||
service.Hosts = ServiceHelper.translateHostsEntriesToHostnameIP(service.Hosts);
|
||||
}
|
||||
|
||||
function transformResources(service) {
|
||||
@@ -314,8 +398,15 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
service.ReservationMemoryBytes = service.ReservationMemoryBytes / 1024 / 1024 || 0;
|
||||
}
|
||||
|
||||
function transformDurations(service) {
|
||||
service.RestartDelay = ServiceHelper.translateNanosToHumanDuration(service.RestartDelay) || '5s';
|
||||
service.RestartWindow = ServiceHelper.translateNanosToHumanDuration(service.RestartWindow) || '0s';
|
||||
service.UpdateDelay = ServiceHelper.translateNanosToHumanDuration(service.UpdateDelay) || '0s';
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
ServiceService.service($transition$.params().id)
|
||||
.then(function success(data) {
|
||||
var service = data;
|
||||
@@ -326,6 +417,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
|
||||
transformResources(service);
|
||||
translateServiceArrays(service);
|
||||
transformDurations(service);
|
||||
$scope.service = service;
|
||||
originalService = angular.copy(service);
|
||||
|
||||
@@ -333,7 +425,9 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
tasks: TaskService.tasks({ service: [service.Name] }),
|
||||
nodes: NodeService.nodes(),
|
||||
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
|
||||
configs: apiVersion >= 1.30 ? ConfigService.configs() : []
|
||||
configs: apiVersion >= 1.30 ? ConfigService.configs() : [],
|
||||
availableImages: ImageService.images(),
|
||||
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25)
|
||||
});
|
||||
})
|
||||
.then(function success(data) {
|
||||
@@ -341,6 +435,8 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
$scope.nodes = data.nodes;
|
||||
$scope.configs = data.configs;
|
||||
$scope.secrets = data.secrets;
|
||||
$scope.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
||||
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
|
||||
|
||||
// Set max cpu value
|
||||
var maxCpus = 0;
|
||||
@@ -355,6 +451,9 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||
$scope.state.sliderMaxCpu = 32;
|
||||
}
|
||||
|
||||
// Default values
|
||||
$scope.state.addSecret = {override: false};
|
||||
|
||||
$timeout(function() {
|
||||
$anchorScroll();
|
||||
});
|
||||
|
||||
@@ -8,133 +8,17 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-list-alt" title="Services">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.service"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add service</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('StackName')">
|
||||
Stack
|
||||
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Image')">
|
||||
Image
|
||||
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Mode')">
|
||||
Scheduling mode
|
||||
<span ng-show="sortType == 'Mode' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Mode' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Ports')">
|
||||
Published Ports
|
||||
<span ng-show="sortType == 'Ports' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Ports' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('UpdatedAt')">
|
||||
Updated at
|
||||
<span ng-show="sortType == 'UpdatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'UpdatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="service in (state.filteredServices = ( services | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="service.Checked" ng-change="selectItem(service)"/></td>
|
||||
<td><a ui-sref="service({id: service.Id})">{{ service.Name }}</a></td>
|
||||
<td>{{ service.StackName ? service.StackName : '-' }}</td>
|
||||
<td>{{ service.Image | hideshasum }}</td>
|
||||
<td>
|
||||
{{ service.Mode }}
|
||||
<code data-toggle="tooltip" title="Replicas">{{ service.Running }}</code>
|
||||
/
|
||||
<code data-toggle="tooltip" title="Replicas">{{ service.Replicas }}</code>
|
||||
<span ng-if="service.Mode === 'replicated' && !service.Scale">
|
||||
<a class="interactive" ng-click="service.Scale = true; service.ReplicaCount = service.Replicas;"><i class="fa fa-arrows-v" aria-hidden="true"></i> Scale</a>
|
||||
</span>
|
||||
<span ng-if="service.Mode === 'replicated' && service.Scale">
|
||||
<input class="input-sm" type="number" ng-model="service.Replicas" />
|
||||
<a class="interactive" ng-click="service.Scale = false;"><i class="fa fa-times"></i></a>
|
||||
<a class="interactive" ng-click="scaleService(service)"><i class="fa fa-check-square-o"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-if="service.Ports && service.Ports.length > 0 && swarmManagerIP" ng-repeat="p in service.Ports" class="image-tag" ng-href="http://{{swarmManagerIP}}:{{p.PublishedPort}}" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
|
||||
</a>
|
||||
<span ng-if="!service.Ports || service.Ports.length === 0 || !swarmManagerIP" >-</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ service.UpdatedAt|getisodate }}
|
||||
</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="service.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ service.ResourceControl.Ownership ? service.ResourceControl.Ownership : service.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!services">
|
||||
<td colspan="7" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="services.length == 0">
|
||||
<td colspan="7" class="text-center text-muted">No services available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="services" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<services-datatable
|
||||
title="Services" title-icon="fa-list-alt"
|
||||
dataset="services" table-key="services"
|
||||
order-by="Name" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
scale-action="scaleAction"
|
||||
force-update-action="forceUpdateAction"
|
||||
swarm-manager-ip="swarmManagerIP"
|
||||
show-force-update-button="applicationState.endpoint.apiVersion >= 1.25"
|
||||
></services-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,80 +1,83 @@
|
||||
angular.module('services', [])
|
||||
.controller('ServicesController', ['$q', '$scope', '$transition$', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Pagination', 'Task', 'Node', 'NodeHelper', 'ModalService', 'ResourceControlService',
|
||||
function ($q, $scope, $transition$, $state, Service, ServiceService, ServiceHelper, Notifications, Pagination, Task, Node, NodeHelper, ModalService, ResourceControlService) {
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('services');
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
.controller('ServicesController', ['$q', '$scope', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Task', 'Node', 'NodeHelper', 'ModalService',
|
||||
function ($q, $scope, $state, Service, ServiceService, ServiceHelper, Notifications, Task, Node, NodeHelper, ModalService) {
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('services', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.scaleService = function scaleService(service) {
|
||||
$scope.scaleAction = function scaleService(service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas);
|
||||
$state.reload();
|
||||
}, function (e) {
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to scale service');
|
||||
service.Scale = false;
|
||||
service.Replicas = service.ReplicaCount;
|
||||
Notifications.error('Failure', e, 'Unable to scale service');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function() {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
||||
$scope.forceUpdateAction = function(selectedItems) {
|
||||
ModalService.confirmServiceForceUpdate(
|
||||
'Do you want to force update of selected service(s)? All the tasks associated to the selected service(s) will be recreated.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeServices();
|
||||
forceUpdateServices(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function removeServices() {
|
||||
angular.forEach($scope.services, function (service) {
|
||||
if (service.Checked) {
|
||||
ServiceService.remove(service)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully deleted');
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
});
|
||||
}
|
||||
function forceUpdateServices(services) {
|
||||
var actionCount = services.length;
|
||||
angular.forEach(services, function (service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
// As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
|
||||
// value or an increment of the counter value to force an update.
|
||||
config.TaskTemplate.ForceUpdate++;
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully updated', service.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to force update service', service.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mapUsersToServices(users) {
|
||||
angular.forEach($scope.services, function (service) {
|
||||
if (service.Metadata) {
|
||||
var serviceRC = service.Metadata.ResourceControl;
|
||||
if (serviceRC && serviceRC.OwnerId !== $scope.user.ID) {
|
||||
angular.forEach(users, function (user) {
|
||||
if (serviceRC.OwnerId === user.Id) {
|
||||
service.Owner = user.Username;
|
||||
}
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeServices(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function removeServices(services) {
|
||||
var actionCount = services.length;
|
||||
angular.forEach(services, function (service) {
|
||||
ServiceService.remove(service)
|
||||
.then(function success() {
|
||||
Notifications.success('Service successfully removed', service.Name);
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,43 @@
|
||||
<rd-widget-header icon="fa-cogs" title="Application settings"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_donation" class="control-label text-left">
|
||||
Disable donation header
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_donation" ng-model="formValues.donationHeader"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- logo -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_logo" class="control-label text-left">
|
||||
Use custom logo
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_logo" ng-model="formValues.customLogo"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="formValues.customLogo">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can specify the URL to your logo here. For an optimal display, logo dimensions should be 155px by 55px.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="logo_url" class="col-sm-1 control-label text-left">
|
||||
URL
|
||||
</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="settings.LogoURL" id="logo_url" placeholder="https://mycompany.com/logo.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !logo -->
|
||||
<!-- security -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Security
|
||||
@@ -36,36 +73,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- security -->
|
||||
<!-- logo -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Logo
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_logo" class="control-label text-left">
|
||||
Use custom logo
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_logo" ng-model="formValues.customLogo"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="formValues.customLogo">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can specify the URL to your logo here. For an optimal display, logo dimensions should be 155px by 55px.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="logo_url" class="col-sm-1 control-label text-left">
|
||||
URL
|
||||
</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="settings.LogoURL" id="logo_url" placeholder="https://mycompany.com/logo.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !logo -->
|
||||
<!-- app-templates -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
App Templates
|
||||
|
||||
@@ -9,6 +9,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_
|
||||
$scope.formValues = {
|
||||
customLogo: false,
|
||||
customTemplates: false,
|
||||
donationHeader: true,
|
||||
externalContributions: false,
|
||||
restrictBindMounts: false,
|
||||
restrictPrivilegedMode: false,
|
||||
@@ -45,6 +46,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_
|
||||
settings.TemplatesURL = DEFAULT_TEMPLATES_URL;
|
||||
}
|
||||
|
||||
settings.DisplayDonationHeader = !$scope.formValues.donationHeader;
|
||||
settings.DisplayExternalContributors = !$scope.formValues.externalContributions;
|
||||
settings.AllowBindMountsForRegularUsers = !$scope.formValues.restrictBindMounts;
|
||||
settings.AllowPrivilegedModeForRegularUsers = !$scope.formValues.restrictPrivilegedMode;
|
||||
@@ -63,6 +65,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_
|
||||
.then(function success(data) {
|
||||
Notifications.success('Settings updated');
|
||||
StateManager.updateLogo(settings.LogoURL);
|
||||
StateManager.updateDonationHeader(settings.DisplayDonationHeader);
|
||||
StateManager.updateExternalContributions(settings.DisplayExternalContributors);
|
||||
if (resetForm) {
|
||||
resetFormValues();
|
||||
@@ -87,6 +90,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_
|
||||
if (settings.TemplatesURL !== DEFAULT_TEMPLATES_URL) {
|
||||
$scope.formValues.customTemplates = true;
|
||||
}
|
||||
$scope.formValues.donationHeader = !settings.DisplayDonationHeader;
|
||||
$scope.formValues.externalContributions = !settings.DisplayExternalContributors;
|
||||
$scope.formValues.restrictBindMounts = !settings.AllowBindMountsForRegularUsers;
|
||||
$scope.formValues.restrictPrivilegedMode = !settings.AllowPrivilegedModeForRegularUsers;
|
||||
|
||||
@@ -22,8 +22,8 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
|
||||
$scope.LDAPSettings.SearchSettings.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.LDAPConnectivityCheck = function() {
|
||||
var settings = $scope.settings;
|
||||
$scope.LDAPConnectivityCheck = function() {
|
||||
var settings = $scope.settings;
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
|
||||
@@ -32,6 +32,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
|
||||
$scope.state.connectivityCheckInProgress = true;
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
|
||||
.then(function success(data) {
|
||||
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
|
||||
return SettingsService.checkLDAPConnectivity(settings);
|
||||
})
|
||||
.then(function success(data) {
|
||||
@@ -60,6 +61,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
|
||||
$scope.state.actionInProgress = true;
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
|
||||
.then(function success(data) {
|
||||
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
|
||||
return SettingsService.update(settings);
|
||||
})
|
||||
.then(function success(data) {
|
||||
@@ -74,6 +76,13 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
|
||||
});
|
||||
};
|
||||
|
||||
// Add default port if :port is not defined in URL
|
||||
function addLDAPDefaultPort(settings, tlsEnabled) {
|
||||
if (settings.LDAPSettings.URL.indexOf(':') === -1) {
|
||||
settings.LDAPSettings.URL += tlsEnabled ? ':636' : ':389';
|
||||
}
|
||||
}
|
||||
|
||||
function initView() {
|
||||
SettingsService.settings()
|
||||
.then(function success(data) {
|
||||
|
||||
@@ -9,75 +9,87 @@
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<ul class="sidebar">
|
||||
<li class="sidebar-title">
|
||||
<span>Active endpoint</span>
|
||||
</li>
|
||||
<li class="sidebar-title"><span>Active endpoint</span></li>
|
||||
<li class="sidebar-title">
|
||||
<select class="select-endpoint form-control" ng-options="endpoint.Name for endpoint in endpoints" ng-model="activeEndpoint" ng-change="switchEndpoint(activeEndpoint)">
|
||||
</select>
|
||||
</li>
|
||||
<li class="sidebar-title"><span>Endpoint actions</span></li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer"></span></a>
|
||||
<a ui-sref="dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket"></span></a>
|
||||
<a ui-sref="templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && displayExternalContributors && ($state.current.name === 'templates' || $state.current.name === 'templates_linuxserver')">
|
||||
<a ui-sref="templates_linuxserver" ui-sref-active="active">LinuxServer.io</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.apiVersion >= 1.25 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="stacks" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list"></span></a>
|
||||
<a ui-sref="stacks" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="services" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt"></span></a>
|
||||
<a ui-sref="services" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="containers" ui-sref-active="active">Containers <span class="menu-icon fa fa-server"></span></a>
|
||||
<a ui-sref="containers" ui-sref-active="active">Containers <span class="menu-icon fa fa-server fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="images" ui-sref-active="active">Images <span class="menu-icon fa fa-clone"></span></a>
|
||||
<a ui-sref="images" ui-sref-active="active">Images <span class="menu-icon fa fa-clone fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="networks" ui-sref-active="active">Networks <span class="menu-icon fa fa-sitemap"></span></a>
|
||||
<a ui-sref="networks" ui-sref-active="active">Networks <span class="menu-icon fa fa-sitemap fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes"></span></a>
|
||||
<a ui-sref="volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.apiVersion >= 1.30 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="configs" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code-o"></span></a>
|
||||
<a ui-sref="configs" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code-o fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.apiVersion >= 1.25 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret"></span></a>
|
||||
<a ui-sref="secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="(applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC') && isAdmin">
|
||||
<a ui-sref="events" ui-sref-active="active">Events <span class="menu-icon fa fa-history"></span></a>
|
||||
<li class="sidebar-list" ng-if="(applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC') && (!applicationState.application.authentication || isAdmin)">
|
||||
<a ui-sref="events" ui-sref-active="active">Events <span class="menu-icon fa fa-history fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || (applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER')">
|
||||
<a ui-sref="swarm" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group"></span></a>
|
||||
<a ui-sref="swarm" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'">
|
||||
<a ui-sref="engine" ui-sref-active="active">Engine <span class="menu-icon fa fa-th"></span></a>
|
||||
<a ui-sref="engine" ui-sref-active="active">Engine <span class="menu-icon fa fa-th fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="applicationState.endpoint.extensions.length > 0 && isAdmin && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<span>Extensions</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.extensions.indexOf('storidge') !== -1 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="storidge.cluster" ui-sref-active="active">Storidge <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'storidge.cluster' || $state.current.name === 'storidge.profiles' || $state.current.name === 'storidge.monitor' || $state.current.name === 'storidge.profiles.create' || $state.current.name === 'storidge.profiles.edit')">
|
||||
<a ui-sref="storidge.monitor" ui-sref-active="active">Monitor</a>
|
||||
</div>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'storidge.cluster' || $state.current.name === 'storidge.profiles' || $state.current.name === 'storidge.monitor' || $state.current.name === 'storidge.profiles.create' || $state.current.name === 'storidge.profiles.edit')">
|
||||
<a ui-sref="storidge.profiles" ui-sref-active="active">Profiles</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="!applicationState.application.authentication || isAdmin || isTeamLeader">
|
||||
<span>Portainer settings</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.application.authentication && (isAdmin || isTeamLeader)">
|
||||
<a ui-sref="users" ui-sref-active="active">User management <span class="menu-icon fa fa-users"></span></a>
|
||||
<a ui-sref="users" ui-sref-active="active">User management <span class="menu-icon fa fa-users fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'users' || $state.current.name === 'user' || $state.current.name === 'teams' || $state.current.name === 'team')">
|
||||
<a ui-sref="teams" ui-sref-active="active">Teams</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<a ui-sref="endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug"></span></a>
|
||||
<a ui-sref="endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<a ui-sref="registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database"></span></a>
|
||||
<a ui-sref="registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<a ui-sref="settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'settings' || $state.current.name === 'settings_authentication') && applicationState.application.authentication && isAdmin">
|
||||
<a ui-sref="settings_authentication" ui-sref-active="active">Authentication</a>
|
||||
<a ui-sref="settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'settings' || $state.current.name === 'settings_authentication' || $state.current.name === 'settings_about') && applicationState.application.authentication && isAdmin">
|
||||
<a ui-sref="settings_authentication" ui-sref-active="active">Authentication</a></div>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'settings' || $state.current.name === 'settings_authentication' || $state.current.name === 'settings_about')">
|
||||
<a ui-sref="settings_about" ui-sref-active="active">About</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('sidebar', [])
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService',
|
||||
function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService) {
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', 'ExtensionManager',
|
||||
function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService, ExtensionManager) {
|
||||
|
||||
$scope.uiVersion = StateManager.getState().application.version;
|
||||
$scope.displayExternalContributors = StateManager.getState().application.displayExternalContributors;
|
||||
@@ -12,8 +12,10 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP
|
||||
var activeEndpointPublicURL = EndpointProvider.endpointPublicURL();
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
|
||||
StateManager.updateEndpointState(true)
|
||||
.then(function success() {
|
||||
ExtensionManager.reset();
|
||||
$state.go('dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
||||
@@ -18,8 +18,31 @@
|
||||
</por-access-control-panel>
|
||||
<!-- !access-control-panel -->
|
||||
|
||||
<por-service-list services="services" nodes="nodes"></por-service-list>
|
||||
<por-task-list tasks="tasks" nodes="nodes"></por-task-list>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<stack-services-datatable
|
||||
title="Services" title-icon="fa-list-alt"
|
||||
dataset="services" table-key="stack-services"
|
||||
order-by="Name"
|
||||
nodes="nodes"
|
||||
public-url="state.publicURL"
|
||||
show-text-filter="true"
|
||||
></stack-services-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<tasks-datatable
|
||||
title="Tasks" title-icon="fa-tasks"
|
||||
dataset="tasks" table-key="stack-tasks"
|
||||
order-by="Updated" reverse-order="true"
|
||||
nodes="nodes"
|
||||
show-text-filter="true"
|
||||
show-slot-column="true"
|
||||
></tasks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="stackFileContent">
|
||||
<div class="col-sm-12">
|
||||
@@ -67,6 +90,22 @@
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
<!-- options -->
|
||||
<div class="col-sm-12 form-section-title" ng-if="applicationState.endpoint.apiVersion >= 1.27">
|
||||
Options
|
||||
</div>
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.27">
|
||||
<div class="col-sm-12">
|
||||
<label for="prune" class="control-label text-left">
|
||||
Prune services
|
||||
<portainer-tooltip position="bottom" message="Prune services that are no longer referenced."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input name="prune" type="checkbox" ng-model="formValues.Prune"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !options -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
angular.module('stack', [])
|
||||
.controller('StackController', ['$q', '$scope', '$state', '$stateParams', '$document', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ServiceHelper', 'CodeMirrorService', 'Notifications', 'FormHelper',
|
||||
function ($q, $scope, $state, $stateParams, $document, StackService, NodeService, ServiceService, TaskService, ServiceHelper, CodeMirrorService, Notifications, FormHelper) {
|
||||
.controller('StackController', ['$q', '$scope', '$state', '$transition$', '$document', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ServiceHelper', 'CodeMirrorService', 'Notifications', 'FormHelper', 'EndpointProvider',
|
||||
function ($q, $scope, $state, $transition$, $document, StackService, NodeService, ServiceService, TaskService, ServiceHelper, CodeMirrorService, Notifications, FormHelper, EndpointProvider) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
actionInProgress: false,
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Prune: false
|
||||
};
|
||||
|
||||
$scope.deployStack = function () {
|
||||
@@ -11,9 +16,10 @@ function ($q, $scope, $state, $stateParams, $document, StackService, NodeService
|
||||
// the value directly from the editor.
|
||||
var stackFile = $scope.editor.getValue();
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StackService.updateStack($scope.stack.Id, stackFile, env)
|
||||
StackService.updateStack($scope.stack.Id, stackFile, env, prune)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$state.reload();
|
||||
@@ -35,7 +41,8 @@ function ($q, $scope, $state, $stateParams, $document, StackService, NodeService
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var stackId = $stateParams.id;
|
||||
var stackId = $transition$.params().id;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
StackService.stack(stackId)
|
||||
.then(function success(data) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<rd-header-content>Stacks</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="state.DisplayInformationPanel">
|
||||
<div class="row" ng-if="state.displayInformationPanel">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-info-circle" title="Information"></rd-widget-header>
|
||||
@@ -27,7 +27,7 @@
|
||||
Display external stacks
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.DisplayExternalStacks"><i></i>
|
||||
<input type="checkbox" ng-model="state.displayExternalStacks"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,81 +39,13 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-th-list" title="Stacks">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.stack"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="stack in (state.filteredStacks = ( stacks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-if="state.DisplayExternalStacks || (!state.DisplayExternalStacks && !stack.External)">
|
||||
<td><input type="checkbox" ng-model="stack.Checked" ng-change="selectItem(stack)" ng-disabled="!stack.Id"/></td>
|
||||
<td>
|
||||
<span ng-if="stack.Id">
|
||||
<a ui-sref="stack({ id: stack.Id })">{{ stack.Name }}</a>
|
||||
</span>
|
||||
<span ng-if="!stack.Id">
|
||||
{{ stack.Name }} <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="stack.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ stack.ResourceControl.Ownership ? stack.ResourceControl.Ownership : stack.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!stacks">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="stacks.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No stacks available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="stacks" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
<rd-widget>
|
||||
<stacks-datatable
|
||||
title="Stacks" title-icon="fa-th-list"
|
||||
dataset="stacks" table-key="stacks"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
display-external-stacks="state.displayExternalStacks"
|
||||
></stacks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,63 +1,39 @@
|
||||
angular.module('stacks', [])
|
||||
.controller('StacksController', ['$scope', 'Notifications', 'Pagination', 'StackService', 'ModalService',
|
||||
function ($scope, Notifications, Pagination, StackService, ModalService) {
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('stacks');
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
$scope.state.DisplayInformationPanel = false;
|
||||
$scope.state.DisplayExternalStacks = true;
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('stacks', $scope.state.pagination_count);
|
||||
.controller('StacksController', ['$scope', '$state', 'Notifications', 'StackService', 'ModalService',
|
||||
function ($scope, $state, Notifications, StackService, ModalService) {
|
||||
$scope.state = {
|
||||
displayInformationPanel: false,
|
||||
displayExternalStacks: true
|
||||
};
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredStacks, function (stack) {
|
||||
if (stack.Id && stack.Checked !== allSelected) {
|
||||
stack.Checked = allSelected;
|
||||
$scope.selectItem(stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected stack(s)? Associated services will be removed as well.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteSelectedStacks();
|
||||
deleteSelectedStacks(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function deleteSelectedStacks() {
|
||||
angular.forEach($scope.stacks, function (stack) {
|
||||
if (stack.Checked) {
|
||||
StackService.remove(stack)
|
||||
.then(function success() {
|
||||
Notifications.success('Stack deleted', stack.Name);
|
||||
var index = $scope.stacks.indexOf(stack);
|
||||
$scope.stacks.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
|
||||
});
|
||||
}
|
||||
function deleteSelectedStacks(stacks) {
|
||||
var actionCount = stacks.length;
|
||||
angular.forEach(stacks, function (stack) {
|
||||
StackService.remove(stack)
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully removed', stack.Name);
|
||||
var index = $scope.stacks.indexOf(stack);
|
||||
$scope.stacks.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +44,7 @@ function ($scope, Notifications, Pagination, StackService, ModalService) {
|
||||
for (var i = 0; i < stacks.length; i++) {
|
||||
var stack = stacks[i];
|
||||
if (stack.External) {
|
||||
$scope.state.DisplayInformationPanel = true;
|
||||
$scope.state.displayInformationPanel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-169
@@ -8,7 +8,7 @@
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-object-group" title="Cluster status"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
@@ -60,7 +60,7 @@
|
||||
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
|
||||
<a ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -72,173 +72,20 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-hdd-o" title="Node status">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('cpu')">
|
||||
CPU
|
||||
<span ng-show="sortType == 'cpu' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'cpu' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('memory')">
|
||||
Memory
|
||||
<span ng-show="sortType == 'memory' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'memory' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('ip')">
|
||||
IP
|
||||
<span ng-show="sortType == 'ip' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ip' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('version')">
|
||||
Engine
|
||||
<span ng-show="sortType == 'version' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'version' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('status')">
|
||||
Status
|
||||
<span ng-show="sortType == 'status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="node in (state.filteredNodes = (swarm.Status | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td>{{ node.name }}</td>
|
||||
<td>{{ node.cpu }}</td>
|
||||
<td>{{ node.memory }}</td>
|
||||
<td>{{ node.ip }}</td>
|
||||
<td>{{ node.version }}</td>
|
||||
<td><span class="label label-{{ node.status|nodestatusbadge }}">{{ node.status }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
|
||||
<nodes-ss-datatable
|
||||
title="Nodes" title-icon="fa-hdd-o"
|
||||
dataset="swarm.Status" table-key="nodes"
|
||||
order-by="name" show-text-filter="true"
|
||||
></nodes-ss-datatable>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-hdd-o" title="Node status">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Hostname')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Hostname' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Hostname' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Role')">
|
||||
Role
|
||||
<span ng-show="sortType == 'Role' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Role' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('CPUs')">
|
||||
CPU
|
||||
<span ng-show="sortType == 'CPUs' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'CPUs' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Memory')">
|
||||
Memory
|
||||
<span ng-show="sortType == 'Memory' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Memory' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('EngineVersion')">
|
||||
Engine
|
||||
<span ng-show="sortType == 'EngineVersion' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'EngineVersion' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.endpoint.apiVersion >= 1.25">
|
||||
<a ui-sref="swarm" ng-click="order('Addr')">
|
||||
IP Address
|
||||
<span ng-show="sortType == 'Addr' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Addr' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Status')">
|
||||
Status
|
||||
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="node in (state.filteredNodes = (nodes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td>
|
||||
<a ui-sref="node({id: node.Id})" ng-if="!applicationState.application.authentication || isAdmin">{{ node.Hostname }}</a>
|
||||
<span ng-if="applicationState.application.authentication && !isAdmin">{{ node.Hostname }}</span>
|
||||
</td>
|
||||
<td>{{ node.Role }}</td>
|
||||
<td>{{ node.CPUs / 1000000000 }}</td>
|
||||
<td>{{ node.Memory|humansize }}</td>
|
||||
<td>{{ node.EngineVersion }}</td>
|
||||
<td ng-if="applicationState.endpoint.apiVersion >= 1.25">{{ node.Addr }}</td>
|
||||
<td><span class="label label-{{ node.Status|nodestatusbadge }}">{{ node.Status }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<nodes-datatable
|
||||
title="Nodes" title-icon="fa-hdd-o"
|
||||
dataset="nodes" table-key="nodes"
|
||||
order-by="Hostname" show-text-filter="true"
|
||||
show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25"
|
||||
access-to-node-details="!applicationState.application.authentication || isAdmin"
|
||||
></nodes-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
angular.module('swarm', [])
|
||||
.controller('SwarmController', ['$q', '$scope', 'SystemService', 'NodeService', 'Pagination', 'Notifications', 'StateManager', 'Authentication',
|
||||
function ($q, $scope, SystemService, NodeService, Pagination, Notifications, StateManager, Authentication) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('swarm_nodes');
|
||||
$scope.sortType = 'Spec.Role';
|
||||
$scope.sortReverse = false;
|
||||
.controller('SwarmController', ['$q', '$scope', 'SystemService', 'NodeService', 'Notifications', 'StateManager', 'Authentication',
|
||||
function ($q, $scope, SystemService, NodeService, Notifications, StateManager, Authentication) {
|
||||
$scope.info = {};
|
||||
$scope.docker = {};
|
||||
$scope.swarm = {};
|
||||
$scope.totalCPU = 0;
|
||||
$scope.totalMemory = 0;
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('swarm_nodes', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
function extractSwarmInfo(info) {
|
||||
// Swarm info is available in SystemStatus object
|
||||
var systemStatus = info.SystemStatus;
|
||||
|
||||
@@ -50,6 +50,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form class="form-horizontal">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Refresh
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="refreshRate" class="col-sm-1 margin-sm-top control-label text-left">
|
||||
Rate
|
||||
<i id="refreshRateChange" class="fa fa-check green-icon" aria-hidden="true" style="display: none;"></i>
|
||||
</label>
|
||||
<div class="col-sm-2">
|
||||
<select id="refreshRate" ng-model="state.refreshRate" ng-change="changeUpdateRepeater()" class="form-control">
|
||||
<option value="5">5s</option>
|
||||
<option value="10">10s</option>
|
||||
<option value="30">30s</option>
|
||||
<option value="60">60s</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
@@ -73,6 +92,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>{{ node.Role }}</div>
|
||||
<div>CPU: {{ node.CPUs / 1000000000 }}</div>
|
||||
<div>Memory: {{ node.Memory|humansize: 2 }}</div>
|
||||
<div><span class="label label-{{ node.Status | nodestatusbadge }}">{{ node.Status }}</span></div>
|
||||
</div>
|
||||
<div class="tasks">
|
||||
<div class="task task_{{ task.Status.State | visualizerTask }}" ng-repeat="task in node.Tasks | filter: (state.DisplayOnlyRunningTasks || '') && { Status: { State: 'running' } }">
|
||||
@@ -80,6 +102,8 @@
|
||||
<div>Image: {{ task.Spec.ContainerSpec.Image | hideshasum }}</div>
|
||||
<div>Status: {{ task.Status.State }}</div>
|
||||
<div>Update: {{ task.Updated | getisodate }}</div>
|
||||
<div ng-if="task.Spec.Resources.Limits.MemoryBytes">Memory limit: {{ task.Spec.Resources.Limits.MemoryBytes | humansize: 2:2 }}</div>
|
||||
<div ng-if="task.Spec.Resources.Limits.NanoCPUs">CPU limit: {{ task.Spec.Resources.Limits.NanoCPUs / 1000000000 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,57 @@
|
||||
angular.module('swarmVisualizer', [])
|
||||
.controller('SwarmVisualizerController', ['$q', '$scope', '$document', 'NodeService', 'ServiceService', 'TaskService', 'Notifications',
|
||||
function ($q, $scope, $document, NodeService, ServiceService, TaskService, Notifications) {
|
||||
.controller('SwarmVisualizerController', ['$q', '$scope', '$document', '$interval', 'NodeService', 'ServiceService', 'TaskService', 'Notifications',
|
||||
function ($q, $scope, $document, $interval, NodeService, ServiceService, TaskService, Notifications) {
|
||||
|
||||
$scope.state = {
|
||||
ShowInformationPanel: true,
|
||||
DisplayOnlyRunningTasks: false
|
||||
DisplayOnlyRunningTasks: false,
|
||||
refreshRate: '5'
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
stopRepeater();
|
||||
});
|
||||
|
||||
$scope.changeUpdateRepeater = function() {
|
||||
stopRepeater();
|
||||
setUpdateRepeater();
|
||||
$('#refreshRateChange').show();
|
||||
$('#refreshRateChange').fadeOut(1500);
|
||||
};
|
||||
|
||||
function stopRepeater() {
|
||||
var repeater = $scope.repeater;
|
||||
if (angular.isDefined(repeater)) {
|
||||
$interval.cancel(repeater);
|
||||
repeater = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setUpdateRepeater() {
|
||||
var refreshRate = $scope.state.refreshRate;
|
||||
$scope.repeater = $interval(function() {
|
||||
$q.all({
|
||||
nodes: NodeService.nodes(),
|
||||
services: ServiceService.services(),
|
||||
tasks: TaskService.tasks()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var nodes = data.nodes;
|
||||
$scope.nodes = nodes;
|
||||
var services = data.services;
|
||||
$scope.services = services;
|
||||
var tasks = data.tasks;
|
||||
$scope.tasks = tasks;
|
||||
prepareVisualizerData(nodes, services, tasks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
stopRepeater();
|
||||
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
|
||||
});
|
||||
}, refreshRate * 1000);
|
||||
}
|
||||
|
||||
|
||||
function assignServiceName(services, tasks) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
var service = services[i];
|
||||
@@ -60,6 +105,7 @@ function ($q, $scope, $document, NodeService, ServiceService, TaskService, Notif
|
||||
var tasks = data.tasks;
|
||||
$scope.tasks = tasks;
|
||||
prepareVisualizerData(nodes, services, tasks);
|
||||
setUpdateRepeater();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize cluster visualizer');
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<span style="margin-left: 5px;">
|
||||
<a class="btn-outline-secondary" ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
|
||||
<a ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -143,7 +143,7 @@
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<span style="margin-left: 5px;" ng-if="isAdmin || user.TeamRole === 'Member'">
|
||||
<a class="btn-outline-secondary" ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
|
||||
<a ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -151,8 +151,8 @@
|
||||
<i ng-if="user.TeamRole === 'Member'" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.TeamRole }}
|
||||
<span style="margin-left: 5px;" ng-if="isAdmin">
|
||||
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a>
|
||||
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a>
|
||||
<a style="margin-left: 5px;" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a>
|
||||
<a style="margin-left: 5px;" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
angular.module('team', [])
|
||||
.controller('TeamController', ['$q', '$scope', '$state', '$transition$', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
.controller('TeamController', ['$q', '$scope', '$state', '$transition$', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'PaginationService', 'Authentication',
|
||||
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, PaginationService, Authentication) {
|
||||
|
||||
$scope.state = {
|
||||
pagination_count_users: Pagination.getPaginationCount('team_available_users'),
|
||||
pagination_count_members: Pagination.getPaginationCount('team_members')
|
||||
pagination_count_users: PaginationService.getPaginationLimit('team_available_users'),
|
||||
pagination_count_members: PaginationService.getPaginationLimit('team_members')
|
||||
};
|
||||
$scope.sortTypeUsers = 'Username';
|
||||
$scope.sortReverseUsers = true;
|
||||
@@ -18,7 +18,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember
|
||||
};
|
||||
|
||||
$scope.changePaginationCountUsers = function() {
|
||||
Pagination.setPaginationCount('team_available_users', $scope.state.pagination_count_users);
|
||||
PaginationService.setPaginationLimit('team_available_users', $scope.state.pagination_count_users);
|
||||
};
|
||||
|
||||
$scope.sortTypeGroupMembers = 'TeamRole';
|
||||
@@ -30,7 +30,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember
|
||||
};
|
||||
|
||||
$scope.changePaginationCountGroupMembers = function() {
|
||||
Pagination.setPaginationCount('team_members', $scope.state.pagination_count_members);
|
||||
PaginationService.setPaginationLimit('team_members', $scope.state.pagination_count_members);
|
||||
};
|
||||
|
||||
$scope.deleteTeam = function() {
|
||||
|
||||
@@ -65,67 +65,12 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-users" title="Teams">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left" ng-if="isAdmin">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-if="isAdmin">
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="team in (state.filteredTeams = (teams | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td ng-if="isAdmin"><input type="checkbox" ng-model="team.Checked" ng-change="selectItem(team)" /></td>
|
||||
<td>{{ team.Name }}</td>
|
||||
<td>
|
||||
<a ui-sref="team({id: team.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!teams">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="teams.length == 0">
|
||||
<td colspan="3" class="text-center text-muted">No teams available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="teams" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<teams-datatable
|
||||
title="Teams" title-icon="fa-users"
|
||||
dataset="teams" table-key="teams"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
></teams-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,17 @@
|
||||
angular.module('teams', [])
|
||||
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication',
|
||||
function ($q, $scope, $state, TeamService, UserService, ModalService, Notifications, Authentication) {
|
||||
$scope.state = {
|
||||
userGroupGroupCreationError: '',
|
||||
selectedItemCount: 0,
|
||||
validName: false,
|
||||
pagination_count: Pagination.getPaginationCount('teams'),
|
||||
actionInProgress: false
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
Leaders: []
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('teams', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredTeams, function (team) {
|
||||
if (team.Checked !== allSelected) {
|
||||
team.Checked = allSelected;
|
||||
$scope.selectItem(team);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.checkNameValidity = function() {
|
||||
var valid = true;
|
||||
for (var i = 0; i < $scope.teams.length; i++) {
|
||||
@@ -76,32 +46,37 @@ function ($q, $scope, $state, TeamService, UserService, TeamMembershipService, M
|
||||
});
|
||||
};
|
||||
|
||||
function deleteSelectedTeams() {
|
||||
angular.forEach($scope.teams, function (team) {
|
||||
if (team.Checked) {
|
||||
TeamService.deleteTeam(team.Id)
|
||||
.then(function success(data) {
|
||||
var index = $scope.teams.indexOf(team);
|
||||
$scope.teams.splice(index, 1);
|
||||
Notifications.success('Team successfully deleted', team.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove team');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to delete the selected team(s)? Users in the team(s) will not be deleted.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteSelectedTeams();
|
||||
deleteSelectedTeams(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function deleteSelectedTeams(selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (team) {
|
||||
TeamService.deleteTeam(team.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Team successfully removed', team.Name);
|
||||
var index = $scope.teams.indexOf(team);
|
||||
$scope.teams.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove team');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
|
||||
@@ -288,6 +288,35 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- !extra-host -->
|
||||
<!-- Label -->
|
||||
<div class="form-group" >
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Labels</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addLabel()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in state.selectedTemplate.Labels" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
</div>
|
||||
<!-- !Label -->
|
||||
</div>
|
||||
<!-- !advanced-options -->
|
||||
<!-- actions -->
|
||||
@@ -356,8 +385,8 @@
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div ng-click="updateCategories(templates, state.filters.Type)">
|
||||
<input type="radio" id="registry_quay" ng-model="state.filters.Type" value="stack">
|
||||
<label for="registry_quay">
|
||||
<input type="radio" id="template_stack" ng-model="state.filters.Type" value="stack">
|
||||
<label for="template_stack">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Stack
|
||||
@@ -366,8 +395,8 @@
|
||||
</label>
|
||||
</div>
|
||||
<div ng-click="updateCategories(templates, state.filters.Type)">
|
||||
<input type="radio" id="registry_custom" ng-model="state.filters.Type" value="container">
|
||||
<label for="registry_custom">
|
||||
<input type="radio" id="template_container" ng-model="state.filters.Type" value="container">
|
||||
<label for="template_container">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-server" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Container
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('templates', [])
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
|
||||
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'PaginationService', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
|
||||
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, PaginationService, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
@@ -45,6 +45,14 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
|
||||
$scope.state.selectedTemplate.Hosts.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLabel = function () {
|
||||
$scope.state.selectedTemplate.Labels.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeLabel = function(index) {
|
||||
$scope.state.selectedTemplate.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
@@ -122,7 +130,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var userId = userDetails.ID;
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
@@ -241,12 +249,13 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
|
||||
$scope.templatesKey = templatesKey;
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.isAdmin = userDetails.role === 1 ? true : false;
|
||||
$scope.isAdmin = userDetails.role === 1;
|
||||
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
if (endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' && apiVersion >= 1.25) {
|
||||
if (templatesKey !== 'linuxserver.io'
|
||||
&& endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' && apiVersion >= 1.25) {
|
||||
$scope.state.filters.Type = 'stack';
|
||||
$scope.state.showDeploymentSelector = true;
|
||||
}
|
||||
|
||||
@@ -113,91 +113,13 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-user" title="Users">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left" ng-if="isAdmin">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-if="isAdmin">
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Username')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Username' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Username' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('RoleName')">
|
||||
Role
|
||||
<span ng-show="sortType == 'RoleName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'RoleName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('AuthenticationMethod')">
|
||||
Authentication
|
||||
<span ng-show="sortType == 'AuthenticationMethod' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'AuthenticationMethod' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="isAdmin"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="user in (state.filteredUsers = (users | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td ng-if="isAdmin"><input type="checkbox" ng-model="user.Checked" ng-change="selectItem(user)" /></td>
|
||||
<td>{{ user.Username }}</td>
|
||||
<td>
|
||||
<i ng-if="user.Role === 1" class="fa fa-user-circle-o" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<i ng-if="user.Role !== 1 && !user.isTeamLeader" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<i ng-if="user.isTeamLeader" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.RoleName }}
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="AuthenticationMethod === 1 || user.Id === 1">Internal</span>
|
||||
<span ng-if="AuthenticationMethod === 2 && user.Id !== 1">LDAP</span>
|
||||
</td>
|
||||
<td ng-if="isAdmin">
|
||||
<a ui-sref="user({id: user.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!users">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="users.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No users available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="users" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="col-sm-12">
|
||||
<users-datatable
|
||||
title="Users" title-icon="fa-user"
|
||||
dataset="users" table-key="users"
|
||||
order-by="Username" show-text-filter="true"
|
||||
authentication-method="AuthenticationMethod"
|
||||
remove-action="removeAction"
|
||||
></users-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
angular.module('users', [])
|
||||
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication', 'SettingsService',
|
||||
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication, SettingsService) {
|
||||
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Authentication', 'SettingsService',
|
||||
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) {
|
||||
$scope.state = {
|
||||
userCreationError: '',
|
||||
selectedItemCount: 0,
|
||||
validUsername: false,
|
||||
pagination_count: Pagination.getPaginationCount('users'),
|
||||
actionInProgress: false
|
||||
};
|
||||
$scope.sortType = 'RoleName';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.formValues = {
|
||||
Username: '',
|
||||
@@ -19,32 +15,6 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi
|
||||
Teams: []
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredUsers, function (user) {
|
||||
if (user.Checked !== allSelected) {
|
||||
user.Checked = allSelected;
|
||||
$scope.selectItem(user);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.checkUsernameValidity = function() {
|
||||
var valid = true;
|
||||
for (var i = 0; i < $scope.users.length; i++) {
|
||||
@@ -80,28 +50,33 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi
|
||||
});
|
||||
};
|
||||
|
||||
function deleteSelectedUsers() {
|
||||
angular.forEach($scope.users, function (user) {
|
||||
if (user.Checked) {
|
||||
UserService.deleteUser(user.Id)
|
||||
.then(function success(data) {
|
||||
var index = $scope.users.indexOf(user);
|
||||
$scope.users.splice(index, 1);
|
||||
Notifications.success('User successfully deleted', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove user');
|
||||
});
|
||||
}
|
||||
function deleteSelectedUsers(selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (user) {
|
||||
UserService.deleteUser(user.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('User successfully removed', user.Username);
|
||||
var index = $scope.users.indexOf(user);
|
||||
$scope.users.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove user');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected users? They will not be able to login into Portainer anymore.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteSelectedUsers();
|
||||
deleteSelectedUsers(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,114 +7,14 @@
|
||||
<rd-header-content>Volumes</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-cubes" title="Volumes">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-primary" type="button" ui-sref="actions.create.volume"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
<span class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
|
||||
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="undefined">
|
||||
All
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="false">
|
||||
Attached
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="true">
|
||||
Unused
|
||||
</label>
|
||||
</span>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Id')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('StackName')">
|
||||
Stack
|
||||
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Driver')">
|
||||
Driver
|
||||
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Mountpoint')">
|
||||
Mount point
|
||||
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="volumes" ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="volume in (state.filteredVolumes = (volumes | filter:{dangling: state.danglingVolumesOnly} | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
|
||||
<td>
|
||||
<a ui-sref="volume({id: volume.Id})" class="monospaced">{{ volume.Id|truncate:25 }}</a>
|
||||
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="volume.dangling">Unused</span></td>
|
||||
</td>
|
||||
<td>{{ volume.StackName ? volume.StackName : '-' }}</td>
|
||||
<td>{{ volume.Driver }}</td>
|
||||
<td>{{ volume.Mountpoint | truncatelr }}</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="volume.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ volume.ResourceControl.Ownership ? volume.ResourceControl.Ownership : volume.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!volumes">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="volumes.length === 0 || state.filteredVolumes.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No volumes available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="volumes" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<volumes-datatable
|
||||
title="Volumes" title-icon="fa-cubes"
|
||||
dataset="volumes" table-key="volumes"
|
||||
order-by="Id" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
></volumes-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,67 +1,32 @@
|
||||
angular.module('volumes', [])
|
||||
.controller('VolumesController', ['$q', '$scope', 'VolumeService', 'Notifications', 'Pagination',
|
||||
function ($q, $scope, VolumeService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('volumes');
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.sortType = 'Id';
|
||||
$scope.sortReverse = false;
|
||||
.controller('VolumesController', ['$q', '$scope', '$state', 'VolumeService', 'Notifications',
|
||||
function ($q, $scope, $state, VolumeService, Notifications) {
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('volumes', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredVolumes, function (volume) {
|
||||
if (volume.Checked !== allSelected) {
|
||||
volume.Checked = allSelected;
|
||||
$scope.selectItem(volume);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
angular.forEach($scope.volumes, function (volume) {
|
||||
if (volume.Checked) {
|
||||
VolumeService.remove(volume)
|
||||
.then(function success() {
|
||||
Notifications.success('Volume deleted', volume.Id);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
$scope.volumes.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove volume');
|
||||
});
|
||||
}
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (volume) {
|
||||
VolumeService.remove(volume)
|
||||
.then(function success() {
|
||||
Notifications.success('Volume successfully removed', volume.Id);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
$scope.volumes.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove volume');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
|
||||
$q.all({
|
||||
attached: VolumeService.volumes({
|
||||
filters: {
|
||||
'dangling': ['false']
|
||||
}
|
||||
}),
|
||||
dangling: VolumeService.volumes({
|
||||
filters: {
|
||||
'dangling': ['true']
|
||||
}
|
||||
})
|
||||
attached: VolumeService.volumes({ filters: { 'dangling': ['false'] } }),
|
||||
dangling: VolumeService.volumes({ filters: { 'dangling': ['true'] } })
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.volumes = data.attached.map(function(volume) {
|
||||
@@ -75,5 +40,6 @@ function ($q, $scope, VolumeService, Notifications, Pagination) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
|
||||
+2
-1
@@ -11,4 +11,5 @@ angular.module('portainer')
|
||||
.constant('API_ENDPOINT_TEAM_MEMBERSHIPS', 'api/team_memberships')
|
||||
.constant('API_ENDPOINT_TEMPLATES', 'api/templates')
|
||||
.constant('DEFAULT_TEMPLATES_URL', 'https://raw.githubusercontent.com/portainer/templates/master/templates.json')
|
||||
.constant('PAGINATION_MAX_ITEMS', 10);
|
||||
.constant('PAGINATION_MAX_ITEMS', 10)
|
||||
.constant('APPLICATION_CACHE_VALIDITY', 3600);
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
&& !$ctrl.state.editOwnership
|
||||
&& ($ctrl.isAdmin || $ctrl.state.canEditOwnership)">
|
||||
<td colspan="2">
|
||||
<a class="btn-outline-secondary" ng-click="$ctrl.state.editOwnership = true"><i class="fa fa-edit space-right" aria-hidden="true"></i>Change ownership</a>
|
||||
<a ng-click="$ctrl.state.editOwnership = true"><i class="fa fa-edit space-right" aria-hidden="true"></i>Change ownership</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !edit-ownership -->
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
angular.module('portainer')
|
||||
.controller('porAccessManagementController', ['AccessService', 'Pagination', 'Notifications',
|
||||
function (AccessService, Pagination, Notifications) {
|
||||
.controller('porAccessManagementController', ['AccessService', 'PaginationService', 'Notifications',
|
||||
function (AccessService, PaginationService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.state = {
|
||||
pagination_count_accesses: Pagination.getPaginationCount('access_management_accesses'),
|
||||
pagination_count_authorizedAccesses: Pagination.getPaginationCount('access_management_AuthorizedAccesses'),
|
||||
pagination_count_accesses: PaginationService.getPaginationLimit('access_management_accesses'),
|
||||
pagination_count_authorizedAccesses: PaginationService.getPaginationLimit('access_management_AuthorizedAccesses'),
|
||||
sortAccessesBy: 'Type',
|
||||
sortAccessesReverse: false,
|
||||
sortAuthorizedAccessesBy: 'Type',
|
||||
@@ -23,11 +23,11 @@ function (AccessService, Pagination, Notifications) {
|
||||
};
|
||||
|
||||
ctrl.changePaginationCountAuthorizedAccesses = function() {
|
||||
Pagination.setPaginationCount('access_management_AuthorizedAccesses', ctrl.state.pagination_count_authorizedAccesses);
|
||||
PaginationService.setPaginationLimit('access_management_AuthorizedAccesses', ctrl.state.pagination_count_authorizedAccesses);
|
||||
};
|
||||
|
||||
ctrl.changePaginationCountAccesses = function() {
|
||||
Pagination.setPaginationCount('access_management_accesses', ctrl.state.pagination_count_accesses);
|
||||
PaginationService.setPaginationLimit('access_management_accesses', ctrl.state.pagination_count_accesses);
|
||||
};
|
||||
|
||||
function dispatchUserAndTeamIDs(accesses, users, teams) {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
angular
|
||||
.module('portainer')
|
||||
angular.module('portainer')
|
||||
.directive('autoFocus', ['$timeout', function porAutoFocus($timeout) {
|
||||
var directive = {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element) {
|
||||
link: function(scope, element) {
|
||||
$timeout(function() {
|
||||
$element[0].focus();
|
||||
element[0].focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular
|
||||
.module('portainer')
|
||||
.directive('rdHeaderTitle', ['Authentication', function rdHeaderTitle(Authentication) {
|
||||
.directive('rdHeaderTitle', ['Authentication', 'StateManager', function rdHeaderTitle(Authentication, StateManager) {
|
||||
var directive = {
|
||||
requires: '^rdHeader',
|
||||
scope: {
|
||||
@@ -8,9 +8,10 @@ angular
|
||||
},
|
||||
link: function (scope, iElement, iAttrs) {
|
||||
scope.username = Authentication.getUserDetails().username;
|
||||
scope.displayDonationHeader = StateManager.getState().application.displayDonationHeader;
|
||||
},
|
||||
transclude: true,
|
||||
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span></div>',
|
||||
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span><a ng-if="displayDonationHeader" ui-sref="settings_about" class="pull-right" style="font-size:14px;margin-right:15px;margin-top:2px;"><span class="fa fa-heart fa-fw red-icon"></span> Help support portainer</a></div>',
|
||||
restrict: 'E'
|
||||
};
|
||||
return directive;
|
||||
|
||||
@@ -3,6 +3,7 @@ angular.module('portainer').component('porImageRegistry', {
|
||||
controller: 'porImageRegistryController',
|
||||
bindings: {
|
||||
'image': '=',
|
||||
'registry': '='
|
||||
'registry': '=',
|
||||
'autoComplete': '<'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<div>
|
||||
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11 col-md-6">
|
||||
<input type="text" class="form-control" ng-model="$ctrl.image" id="image_name" placeholder="e.g. myImage:myTag">
|
||||
<input type="text" class="form-control" uib-typeahead="image for image in $ctrl.availableImages | filter:$viewValue | limitTo:5"
|
||||
ng-model="$ctrl.image" id="image_name" placeholder="e.g. myImage:myTag">
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-2 col-md-1 margin-sm-top control-label text-left">
|
||||
Registry
|
||||
</label>
|
||||
<div class="col-sm-10 col-md-4 margin-sm-top">
|
||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry" class="form-control"></select>
|
||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,27 +1,29 @@
|
||||
angular.module('portainer')
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, Notifications) {
|
||||
var ctrl = this;
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, ImageService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
if (!ctrl.registry.Id) {
|
||||
ctrl.registry = dockerhub;
|
||||
} else {
|
||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
availableImages: ctrl.autoComplete ? ImageService.images() : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
if (!ctrl.registry.Id) {
|
||||
ctrl.registry = dockerhub;
|
||||
} else {
|
||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
||||
initComponent();
|
||||
}]);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
angular.module('portainer')
|
||||
.directive('onEnterKey', [function porOnEnterKey() {
|
||||
var directive = {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
element.bind('keydown keypress', function (e) {
|
||||
if ( e.which === 13 ) {
|
||||
e.preventDefault();
|
||||
scope.$apply(function () {
|
||||
scope.$eval(attrs.onEnterKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
}]);
|
||||
@@ -1,8 +0,0 @@
|
||||
angular.module('portainer').component('porServiceList', {
|
||||
templateUrl: 'app/directives/serviceList/porServiceList.html',
|
||||
controller: 'porServiceListController',
|
||||
bindings: {
|
||||
'services': '<',
|
||||
'nodes': '<'
|
||||
}
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-list-alt" title="Associated services">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="$ctrl.state.pagination_count" ng-change="$ctrl.changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12">
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="$ctrl.state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.order('Name')">
|
||||
Name
|
||||
<span ng-show="$ctrl.sortType === 'Name' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.sortType === 'Name' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.order('Image')">
|
||||
Image
|
||||
<span ng-show="$ctrl.sortType === 'Image' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.sortType === 'Image' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.order('Mode')">
|
||||
Scheduling mode
|
||||
<span ng-show="$ctrl.sortType === 'Mode' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.sortType === 'Mode' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.order('Ports')">
|
||||
Published Ports
|
||||
<span ng-show="$ctrl.sortType === 'Ports' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.sortType === 'Ports' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.order('UpdatedAt')">
|
||||
Updated at
|
||||
<span ng-show="$ctrl.sortType === 'UpdatedAt' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.sortType === 'UpdatedAt' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="service in $ctrl.services | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage:$ctrl.state.pagination_count" pagination-id="services_list">
|
||||
<td><a ui-sref="service({id: service.Id})">{{ service.Name }}</a></td>
|
||||
<td>{{ service.Image | hideshasum }}</td>
|
||||
<td>
|
||||
{{ service.Mode }}
|
||||
<code data-toggle="tooltip" title="Replicas">{{ service.Tasks | runningtaskscount }}</code>
|
||||
/
|
||||
<code data-toggle="tooltip" title="Replicas">{{ service.Mode === 'replicated' ? service.Replicas : ($ctrl.nodes | availablenodecount) }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-if="service.Ports && service.Ports.length > 0" ng-repeat="p in service.Ports" class="image-tag" ng-href="http://{{$ctrl.state.publicURL}}:{{p.PublishedPort}}" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
|
||||
</a>
|
||||
<span ng-if="!service.Ports || service.Ports.length === 0" >-</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ service.UpdatedAt|getisodate }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.services">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="($ctrl.services | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage: $ctrl.state.pagination_count).length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No services available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="$ctrl.services" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls pagination-id="services_list"></dir-pagination-controls >
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
<rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,21 +0,0 @@
|
||||
angular.module('portainer')
|
||||
.controller('porServiceListController', ['EndpointProvider', 'Pagination',
|
||||
function (EndpointProvider, Pagination) {
|
||||
var ctrl = this;
|
||||
ctrl.state = {
|
||||
pagination_count: Pagination.getPaginationCount('services_list'),
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
ctrl.sortType = 'Name';
|
||||
ctrl.sortReverse = false;
|
||||
|
||||
ctrl.order = function(sortType) {
|
||||
ctrl.sortReverse = (ctrl.sortType === sortType) ? !ctrl.sortReverse : false;
|
||||
ctrl.sortType = sortType;
|
||||
};
|
||||
|
||||
ctrl.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('services_list', ctrl.state.pagination_count);
|
||||
};
|
||||
|
||||
}]);
|
||||
@@ -1,12 +0,0 @@
|
||||
angular.module('portainer').component('porSlider', {
|
||||
templateUrl: 'app/directives/slider/porSlider.html',
|
||||
controller: 'porSliderController',
|
||||
bindings: {
|
||||
model: '=',
|
||||
onChange: '&',
|
||||
floor: '<',
|
||||
ceil: '<',
|
||||
step: '<',
|
||||
precision: '<'
|
||||
}
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
angular.module('portainer').component('porTaskList', {
|
||||
templateUrl: 'app/directives/taskList/porTaskList.html',
|
||||
controller: 'porTaskListController',
|
||||
bindings: {
|
||||
'tasks': '<',
|
||||
'nodes': '<'
|
||||
}
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user