mirror of https://github.com/portainer/portainer
commit
b72cce810e
|
@ -3,7 +3,6 @@
|
||||||
<img title="portainer" src='http://portainer.io/images/logo_alt.png' />
|
<img title="portainer" src='http://portainer.io/images/logo_alt.png' />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[![Microbadger version](https://images.microbadger.com/badges/version/portainer/portainer.svg)](https://microbadger.com/images/portainer/portainer "Latest version on Docker Hub")
|
|
||||||
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size")
|
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size")
|
||||||
[![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/latest/?badge=stable)
|
[![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/latest/?badge=stable)
|
||||||
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
|
@ -67,20 +67,41 @@ func (service *EndpointService) Endpoints() ([]portainer.Endpoint, error) {
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synchronize creates, updates and deletes endpoints inside a single transaction.
|
||||||
|
func (service *EndpointService) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
|
||||||
|
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||||
|
|
||||||
|
for _, endpoint := range toCreate {
|
||||||
|
err := storeNewEndpoint(endpoint, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range toUpdate {
|
||||||
|
err := marshalAndStoreEndpoint(endpoint, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range toDelete {
|
||||||
|
err := bucket.Delete(internal.Itob(int(endpoint.ID)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
||||||
func (service *EndpointService) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
func (service *EndpointService) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||||
|
err := storeNewEndpoint(endpoint, bucket)
|
||||||
id, _ := bucket.NextSequence()
|
|
||||||
endpoint.ID = portainer.EndpointID(id)
|
|
||||||
|
|
||||||
data, err := internal.MarshalEndpoint(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -172,3 +193,23 @@ func (service *EndpointService) DeleteActive() error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func marshalAndStoreEndpoint(endpoint *portainer.Endpoint, bucket *bolt.Bucket) error {
|
||||||
|
data, err := internal.MarshalEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bucket.Put(internal.Itob(int(endpoint.ID)), data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeNewEndpoint(endpoint *portainer.Endpoint, bucket *bolt.Bucket) error {
|
||||||
|
id, _ := bucket.NextSequence()
|
||||||
|
endpoint.ID = portainer.EndpointID(id)
|
||||||
|
|
||||||
|
return marshalAndStoreEndpoint(endpoint, bucket)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/portainer/portainer"
|
"github.com/portainer/portainer"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,8 +15,11 @@ import (
|
||||||
type Service struct{}
|
type Service struct{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errInvalidEnpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://")
|
errInvalidEnpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://")
|
||||||
errSocketNotFound = portainer.Error("Unable to locate Unix socket")
|
errSocketNotFound = portainer.Error("Unable to locate Unix socket")
|
||||||
|
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
|
||||||
|
errInvalidSyncInterval = portainer.Error("Invalid synchronization interval")
|
||||||
|
errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseFlags parse the CLI flags and return a portainer.Flags struct
|
// ParseFlags parse the CLI flags and return a portainer.Flags struct
|
||||||
|
@ -22,17 +27,20 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
kingpin.Version(version)
|
kingpin.Version(version)
|
||||||
|
|
||||||
flags := &portainer.CLIFlags{
|
flags := &portainer.CLIFlags{
|
||||||
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(),
|
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(),
|
||||||
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
||||||
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints").String(),
|
||||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source").Default(defaultSyncInterval).String(),
|
||||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
||||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||||
Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').String(),
|
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||||
TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(),
|
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||||
TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(),
|
Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').String(),
|
||||||
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
|
NoAuth: kingpin.Flag("no-auth", "Disable authentication").Default(defaultNoAuth).Bool(),
|
||||||
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
|
TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(),
|
||||||
|
TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(),
|
||||||
|
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
|
||||||
|
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
kingpin.Parse()
|
kingpin.Parse()
|
||||||
|
@ -41,13 +49,37 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
|
|
||||||
// ValidateFlags validates the values of the flags.
|
// ValidateFlags validates the values of the flags.
|
||||||
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
if *flags.Endpoint != "" {
|
|
||||||
if !strings.HasPrefix(*flags.Endpoint, "unix://") && !strings.HasPrefix(*flags.Endpoint, "tcp://") {
|
if *flags.Endpoint != "" && *flags.ExternalEndpoints != "" {
|
||||||
|
return errEndpointExcludeExternal
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validateEndpoint(*flags.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateExternalEndpoints(*flags.ExternalEndpoints)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateSyncInterval(*flags.SyncInterval)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateEndpoint(endpoint string) error {
|
||||||
|
if endpoint != "" {
|
||||||
|
if !strings.HasPrefix(endpoint, "unix://") && !strings.HasPrefix(endpoint, "tcp://") {
|
||||||
return errInvalidEnpointProtocol
|
return errInvalidEnpointProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(*flags.Endpoint, "unix://") {
|
if strings.HasPrefix(endpoint, "unix://") {
|
||||||
socketPath := strings.TrimPrefix(*flags.Endpoint, "unix://")
|
socketPath := strings.TrimPrefix(endpoint, "unix://")
|
||||||
if _, err := os.Stat(socketPath); err != nil {
|
if _, err := os.Stat(socketPath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return errSocketNotFound
|
return errSocketNotFound
|
||||||
|
@ -56,6 +88,27 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExternalEndpoints(externalEndpoints string) error {
|
||||||
|
if externalEndpoints != "" {
|
||||||
|
if _, err := os.Stat(externalEndpoints); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return errEndpointsFileNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSyncInterval(syncInterval string) error {
|
||||||
|
if syncInterval != defaultSyncInterval {
|
||||||
|
_, err := time.ParseDuration(syncInterval)
|
||||||
|
if err != nil {
|
||||||
|
return errInvalidSyncInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ const (
|
||||||
defaultDataDirectory = "/data"
|
defaultDataDirectory = "/data"
|
||||||
defaultAssetsDirectory = "."
|
defaultAssetsDirectory = "."
|
||||||
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||||
|
defaultNoAuth = "false"
|
||||||
defaultTLSVerify = "false"
|
defaultTLSVerify = "false"
|
||||||
defaultTLSCACertPath = "/certs/ca.pem"
|
defaultTLSCACertPath = "/certs/ca.pem"
|
||||||
defaultTLSCertPath = "/certs/cert.pem"
|
defaultTLSCertPath = "/certs/cert.pem"
|
||||||
defaultTLSKeyPath = "/certs/key.pem"
|
defaultTLSKeyPath = "/certs/key.pem"
|
||||||
|
defaultSyncInterval = "60s"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,10 @@ const (
|
||||||
defaultDataDirectory = "C:\\data"
|
defaultDataDirectory = "C:\\data"
|
||||||
defaultAssetsDirectory = "."
|
defaultAssetsDirectory = "."
|
||||||
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||||
|
defaultNoAuth = "false"
|
||||||
defaultTLSVerify = "false"
|
defaultTLSVerify = "false"
|
||||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||||
|
defaultSyncInterval = "60s"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/portainer/portainer"
|
"github.com/portainer/portainer"
|
||||||
"github.com/portainer/portainer/bolt"
|
"github.com/portainer/portainer/bolt"
|
||||||
"github.com/portainer/portainer/cli"
|
"github.com/portainer/portainer/cli"
|
||||||
|
"github.com/portainer/portainer/cron"
|
||||||
"github.com/portainer/portainer/crypto"
|
"github.com/portainer/portainer/crypto"
|
||||||
"github.com/portainer/portainer/file"
|
"github.com/portainer/portainer/file"
|
||||||
"github.com/portainer/portainer/http"
|
"github.com/portainer/portainer/http"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func initCLI() *portainer.CLIFlags {
|
||||||
var cli portainer.CLIService = &cli.Service{}
|
var cli portainer.CLIService = &cli.Service{}
|
||||||
flags, err := cli.ParseFlags(portainer.APIVersion)
|
flags, err := cli.ParseFlags(portainer.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,37 +24,76 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
settings := &portainer.Settings{
|
func initFileService(dataStorePath string) portainer.FileService {
|
||||||
HiddenLabels: *flags.Labels,
|
fileService, err := file.NewService(dataStorePath, "")
|
||||||
Logo: *flags.Logo,
|
|
||||||
}
|
|
||||||
|
|
||||||
fileService, err := file.NewService(*flags.Data, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return fileService
|
||||||
|
}
|
||||||
|
|
||||||
var store = bolt.NewStore(*flags.Data)
|
func initStore(dataStorePath string) *bolt.Store {
|
||||||
err = store.Open()
|
var store = bolt.NewStore(dataStorePath)
|
||||||
|
err := store.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer store.Close()
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
jwtService, err := jwt.NewService()
|
func initJWTService(authenticationEnabled bool) portainer.JWTService {
|
||||||
|
if authenticationEnabled {
|
||||||
|
jwtService, err := jwt.NewService()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return jwtService
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initCryptoService() portainer.CryptoService {
|
||||||
|
return &crypto.Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initEndpointWatcher(endpointService portainer.EndpointService, externalEnpointFile string, syncInterval string) bool {
|
||||||
|
authorizeEndpointMgmt := true
|
||||||
|
if externalEnpointFile != "" {
|
||||||
|
authorizeEndpointMgmt = false
|
||||||
|
log.Println("Using external endpoint definition. Endpoint management via the API will be disabled.")
|
||||||
|
endpointWatcher := cron.NewWatcher(endpointService, syncInterval)
|
||||||
|
err := endpointWatcher.WatchEndpointFile(externalEnpointFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authorizeEndpointMgmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSettings(authorizeEndpointMgmt bool, flags *portainer.CLIFlags) *portainer.Settings {
|
||||||
|
return &portainer.Settings{
|
||||||
|
HiddenLabels: *flags.Labels,
|
||||||
|
Logo: *flags.Logo,
|
||||||
|
Authentication: !*flags.NoAuth,
|
||||||
|
EndpointManagement: authorizeEndpointMgmt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveFirstEndpointFromDatabase(endpointService portainer.EndpointService) *portainer.Endpoint {
|
||||||
|
endpoints, err := endpointService.Endpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return &endpoints[0]
|
||||||
|
}
|
||||||
|
|
||||||
var cryptoService portainer.CryptoService = &crypto.Service{}
|
func initActiveEndpoint(endpointService portainer.EndpointService, flags *portainer.CLIFlags) *portainer.Endpoint {
|
||||||
|
activeEndpoint, err := endpointService.GetActive()
|
||||||
// Initialize the active endpoint from the CLI only if there is no
|
if err == portainer.ErrEndpointNotFound {
|
||||||
// active endpoint defined yet.
|
if *flags.Endpoint != "" {
|
||||||
var activeEndpoint *portainer.Endpoint
|
|
||||||
if *flags.Endpoint != "" {
|
|
||||||
activeEndpoint, err = store.EndpointService.GetActive()
|
|
||||||
if err == portainer.ErrEndpointNotFound {
|
|
||||||
activeEndpoint = &portainer.Endpoint{
|
activeEndpoint = &portainer.Endpoint{
|
||||||
Name: "primary",
|
Name: "primary",
|
||||||
URL: *flags.Endpoint,
|
URL: *flags.Endpoint,
|
||||||
|
@ -62,30 +102,54 @@ func main() {
|
||||||
TLSCertPath: *flags.TLSCert,
|
TLSCertPath: *flags.TLSCert,
|
||||||
TLSKeyPath: *flags.TLSKey,
|
TLSKeyPath: *flags.TLSKey,
|
||||||
}
|
}
|
||||||
err = store.EndpointService.CreateEndpoint(activeEndpoint)
|
err = endpointService.CreateEndpoint(activeEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if *flags.ExternalEndpoints != "" {
|
||||||
log.Fatal(err)
|
activeEndpoint = retrieveFirstEndpointFromDatabase(endpointService)
|
||||||
}
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return activeEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flags := initCLI()
|
||||||
|
|
||||||
|
fileService := initFileService(*flags.Data)
|
||||||
|
|
||||||
|
store := initStore(*flags.Data)
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
jwtService := initJWTService(!*flags.NoAuth)
|
||||||
|
|
||||||
|
cryptoService := initCryptoService()
|
||||||
|
|
||||||
|
authorizeEndpointMgmt := initEndpointWatcher(store.EndpointService, *flags.ExternalEndpoints, *flags.SyncInterval)
|
||||||
|
|
||||||
|
settings := initSettings(authorizeEndpointMgmt, flags)
|
||||||
|
|
||||||
|
activeEndpoint := initActiveEndpoint(store.EndpointService, flags)
|
||||||
|
|
||||||
var server portainer.Server = &http.Server{
|
var server portainer.Server = &http.Server{
|
||||||
BindAddress: *flags.Addr,
|
BindAddress: *flags.Addr,
|
||||||
AssetsPath: *flags.Assets,
|
AssetsPath: *flags.Assets,
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
TemplatesURL: *flags.Templates,
|
TemplatesURL: *flags.Templates,
|
||||||
UserService: store.UserService,
|
AuthDisabled: *flags.NoAuth,
|
||||||
EndpointService: store.EndpointService,
|
EndpointManagement: authorizeEndpointMgmt,
|
||||||
CryptoService: cryptoService,
|
UserService: store.UserService,
|
||||||
JWTService: jwtService,
|
EndpointService: store.EndpointService,
|
||||||
FileService: fileService,
|
CryptoService: cryptoService,
|
||||||
ActiveEndpoint: activeEndpoint,
|
JWTService: jwtService,
|
||||||
|
FileService: fileService,
|
||||||
|
ActiveEndpoint: activeEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Starting Portainer on %s", *flags.Addr)
|
log.Printf("Starting Portainer on %s", *flags.Addr)
|
||||||
err = server.Start()
|
err := server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
endpointSyncJob struct {
|
||||||
|
logger *log.Logger
|
||||||
|
endpointService portainer.EndpointService
|
||||||
|
endpointFilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronization struct {
|
||||||
|
endpointsToCreate []*portainer.Endpoint
|
||||||
|
endpointsToUpdate []*portainer.Endpoint
|
||||||
|
endpointsToDelete []*portainer.Endpoint
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrEmptyEndpointArray is an error raised when the external endpoint source array is empty.
|
||||||
|
ErrEmptyEndpointArray = portainer.Error("External endpoint source is empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEndpointSyncJob(endpointFilePath string, endpointService portainer.EndpointService) endpointSyncJob {
|
||||||
|
return endpointSyncJob{
|
||||||
|
logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||||
|
endpointService: endpointService,
|
||||||
|
endpointFilePath: endpointFilePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointSyncError(err error, logger *log.Logger) bool {
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("Endpoint synchronization error: %s", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidEndpoint(endpoint *portainer.Endpoint) bool {
|
||||||
|
if endpoint.Name != "" && endpoint.URL != "" {
|
||||||
|
if !strings.HasPrefix(endpoint.URL, "unix://") && !strings.HasPrefix(endpoint.URL, "tcp://") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint) int {
|
||||||
|
for idx, v := range endpoints {
|
||||||
|
if endpoint.Name == v.Name && isValidEndpoint(&v) {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeEndpointIfRequired(original, updated *portainer.Endpoint) *portainer.Endpoint {
|
||||||
|
var endpoint *portainer.Endpoint
|
||||||
|
if original.URL != updated.URL || original.TLS != updated.TLS {
|
||||||
|
endpoint = original
|
||||||
|
endpoint.URL = updated.URL
|
||||||
|
if updated.TLS {
|
||||||
|
endpoint.TLS = true
|
||||||
|
endpoint.TLSCACertPath = updated.TLSCACertPath
|
||||||
|
endpoint.TLSCertPath = updated.TLSCertPath
|
||||||
|
endpoint.TLSKeyPath = updated.TLSKeyPath
|
||||||
|
} else {
|
||||||
|
endpoint.TLS = false
|
||||||
|
endpoint.TLSCACertPath = ""
|
||||||
|
endpoint.TLSCertPath = ""
|
||||||
|
endpoint.TLSKeyPath = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sync synchronization) requireSync() bool {
|
||||||
|
if len(sync.endpointsToCreate) != 0 || len(sync.endpointsToUpdate) != 0 || len(sync.endpointsToDelete) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMP: endpointSyncJob method to access logger, should be generic
|
||||||
|
func (job endpointSyncJob) prepareSyncData(storedEndpoints, fileEndpoints []portainer.Endpoint) *synchronization {
|
||||||
|
endpointsToCreate := make([]*portainer.Endpoint, 0)
|
||||||
|
endpointsToUpdate := make([]*portainer.Endpoint, 0)
|
||||||
|
endpointsToDelete := make([]*portainer.Endpoint, 0)
|
||||||
|
|
||||||
|
for idx := range storedEndpoints {
|
||||||
|
fidx := endpointExists(&storedEndpoints[idx], fileEndpoints)
|
||||||
|
if fidx != -1 {
|
||||||
|
endpoint := mergeEndpointIfRequired(&storedEndpoints[idx], &fileEndpoints[fidx])
|
||||||
|
if endpoint != nil {
|
||||||
|
job.logger.Printf("New definition for a stored endpoint found in file, updating database. [name: %v] [url: %v]\n", endpoint.Name, endpoint.URL)
|
||||||
|
endpointsToUpdate = append(endpointsToUpdate, endpoint)
|
||||||
|
} else {
|
||||||
|
job.logger.Printf("No change detected for a stored endpoint. [name: %v] [url: %v]\n", storedEndpoints[idx].Name, storedEndpoints[idx].URL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
job.logger.Printf("Stored endpoint not found in file (definition might be invalid), removing from database. [name: %v] [url: %v]", storedEndpoints[idx].Name, storedEndpoints[idx].URL)
|
||||||
|
endpointsToDelete = append(endpointsToDelete, &storedEndpoints[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, endpoint := range fileEndpoints {
|
||||||
|
if endpoint.Name == "" || endpoint.URL == "" {
|
||||||
|
job.logger.Printf("Invalid file endpoint definition, skipping. [name: %v] [url: %v]", endpoint.Name, endpoint.URL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sidx := endpointExists(&fileEndpoints[idx], storedEndpoints)
|
||||||
|
if sidx == -1 {
|
||||||
|
job.logger.Printf("File endpoint not found in database, adding to database. [name: %v] [url: %v]", fileEndpoints[idx].Name, fileEndpoints[idx].URL)
|
||||||
|
endpointsToCreate = append(endpointsToCreate, &fileEndpoints[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &synchronization{
|
||||||
|
endpointsToCreate: endpointsToCreate,
|
||||||
|
endpointsToUpdate: endpointsToUpdate,
|
||||||
|
endpointsToDelete: endpointsToDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (job endpointSyncJob) Sync() error {
|
||||||
|
data, err := ioutil.ReadFile(job.endpointFilePath)
|
||||||
|
if endpointSyncError(err, job.logger) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileEndpoints []portainer.Endpoint
|
||||||
|
err = json.Unmarshal(data, &fileEndpoints)
|
||||||
|
if endpointSyncError(err, job.logger) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fileEndpoints) == 0 {
|
||||||
|
return ErrEmptyEndpointArray
|
||||||
|
}
|
||||||
|
|
||||||
|
storedEndpoints, err := job.endpointService.Endpoints()
|
||||||
|
if endpointSyncError(err, job.logger) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sync := job.prepareSyncData(storedEndpoints, fileEndpoints)
|
||||||
|
if sync.requireSync() {
|
||||||
|
err = job.endpointService.Synchronize(sync.endpointsToCreate, sync.endpointsToUpdate, sync.endpointsToDelete)
|
||||||
|
if endpointSyncError(err, job.logger) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
job.logger.Printf("Endpoint synchronization ended. [created: %v] [updated: %v] [deleted: %v]", len(sync.endpointsToCreate), len(sync.endpointsToUpdate), len(sync.endpointsToDelete))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (job endpointSyncJob) Run() {
|
||||||
|
job.logger.Println("Endpoint synchronization job started.")
|
||||||
|
err := job.Sync()
|
||||||
|
endpointSyncError(err, job.logger)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/portainer/portainer"
|
||||||
|
"github.com/robfig/cron"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher represents a service for managing crons.
|
||||||
|
type Watcher struct {
|
||||||
|
Cron *cron.Cron
|
||||||
|
EndpointService portainer.EndpointService
|
||||||
|
syncInterval string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher initializes a new service.
|
||||||
|
func NewWatcher(endpointService portainer.EndpointService, syncInterval string) *Watcher {
|
||||||
|
return &Watcher{
|
||||||
|
Cron: cron.New(),
|
||||||
|
EndpointService: endpointService,
|
||||||
|
syncInterval: syncInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchEndpointFile starts a cron job to synchronize the endpoints from a file
|
||||||
|
func (watcher *Watcher) WatchEndpointFile(endpointFilePath string) error {
|
||||||
|
job := newEndpointSyncJob(endpointFilePath, watcher.EndpointService)
|
||||||
|
|
||||||
|
err := job.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = watcher.Cron.AddJob("@every "+watcher.syncInterval, job)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.Cron.Start()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
type AuthHandler struct {
|
type AuthHandler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
|
authDisabled bool
|
||||||
UserService portainer.UserService
|
UserService portainer.UserService
|
||||||
CryptoService portainer.CryptoService
|
CryptoService portainer.CryptoService
|
||||||
JWTService portainer.JWTService
|
JWTService portainer.JWTService
|
||||||
|
@ -26,6 +27,9 @@ const (
|
||||||
ErrInvalidCredentialsFormat = portainer.Error("Invalid credentials format")
|
ErrInvalidCredentialsFormat = portainer.Error("Invalid credentials format")
|
||||||
// ErrInvalidCredentials is an error raised when credentials for a user are invalid
|
// ErrInvalidCredentials is an error raised when credentials for a user are invalid
|
||||||
ErrInvalidCredentials = portainer.Error("Invalid credentials")
|
ErrInvalidCredentials = portainer.Error("Invalid credentials")
|
||||||
|
// ErrAuthDisabled is an error raised when trying to access the authentication endpoints
|
||||||
|
// when the server has been started with the --no-auth flag
|
||||||
|
ErrAuthDisabled = portainer.Error("Authentication is disabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAuthHandler returns a new instance of AuthHandler.
|
// NewAuthHandler returns a new instance of AuthHandler.
|
||||||
|
@ -44,6 +48,11 @@ func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Reques
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if handler.authDisabled {
|
||||||
|
Error(w, ErrAuthDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req postAuthRequest
|
var req postAuthRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
Error(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
Error(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
||||||
|
|
|
@ -16,13 +16,20 @@ import (
|
||||||
// EndpointHandler represents an HTTP API handler for managing Docker endpoints.
|
// EndpointHandler represents an HTTP API handler for managing Docker endpoints.
|
||||||
type EndpointHandler struct {
|
type EndpointHandler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
EndpointService portainer.EndpointService
|
authorizeEndpointManagement bool
|
||||||
FileService portainer.FileService
|
EndpointService portainer.EndpointService
|
||||||
server *Server
|
FileService portainer.FileService
|
||||||
middleWareService *middleWareService
|
server *Server
|
||||||
|
middleWareService *middleWareService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrEndpointManagementDisabled is an error raised when trying to access the endpoints management endpoints
|
||||||
|
// when the server has been started with the --external-endpoints flag
|
||||||
|
ErrEndpointManagementDisabled = portainer.Error("Endpoint management is disabled")
|
||||||
|
)
|
||||||
|
|
||||||
// NewEndpointHandler returns a new instance of EndpointHandler.
|
// NewEndpointHandler returns a new instance of EndpointHandler.
|
||||||
func NewEndpointHandler(middleWareService *middleWareService) *EndpointHandler {
|
func NewEndpointHandler(middleWareService *middleWareService) *EndpointHandler {
|
||||||
h := &EndpointHandler{
|
h := &EndpointHandler{
|
||||||
|
@ -65,6 +72,11 @@ func (handler *EndpointHandler) handleGetEndpoints(w http.ResponseWriter, r *htt
|
||||||
// if the active URL parameter is specified, will also define the new endpoint as the active endpoint.
|
// if the active URL parameter is specified, will also define the new endpoint as the active endpoint.
|
||||||
// /endpoints(?active=true|false)
|
// /endpoints(?active=true|false)
|
||||||
func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *http.Request) {
|
func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !handler.authorizeEndpointManagement {
|
||||||
|
Error(w, ErrEndpointManagementDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req postEndpointsRequest
|
var req postEndpointsRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
Error(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
Error(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
||||||
|
@ -203,6 +215,11 @@ func (handler *EndpointHandler) handlePostEndpoint(w http.ResponseWriter, r *htt
|
||||||
|
|
||||||
// handlePutEndpoint handles PUT requests on /endpoints/:id
|
// handlePutEndpoint handles PUT requests on /endpoints/:id
|
||||||
func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http.Request) {
|
func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !handler.authorizeEndpointManagement {
|
||||||
|
Error(w, ErrEndpointManagementDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
|
@ -262,6 +279,11 @@ type putEndpointsRequest struct {
|
||||||
// handleDeleteEndpoint handles DELETE requests on /endpoints/:id
|
// handleDeleteEndpoint handles DELETE requests on /endpoints/:id
|
||||||
// DELETE /endpoints/0 deletes the active endpoint
|
// DELETE /endpoints/0 deletes the active endpoint
|
||||||
func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *http.Request) {
|
func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !handler.authorizeEndpointManagement {
|
||||||
|
Error(w, ErrEndpointManagementDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ import (
|
||||||
|
|
||||||
// Service represents a service to manage HTTP middlewares
|
// Service represents a service to manage HTTP middlewares
|
||||||
type middleWareService struct {
|
type middleWareService struct {
|
||||||
jwtService portainer.JWTService
|
jwtService portainer.JWTService
|
||||||
|
authDisabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMiddleware(h http.Handler, middleware ...func(http.Handler) http.Handler) http.Handler {
|
func addMiddleware(h http.Handler, middleware ...func(http.Handler) http.Handler) http.Handler {
|
||||||
|
@ -37,24 +38,26 @@ func (*middleWareService) middleWareSecureHeaders(next http.Handler) http.Handle
|
||||||
// middleWareAuthenticate provides Authentication middleware for handlers
|
// middleWareAuthenticate provides Authentication middleware for handlers
|
||||||
func (service *middleWareService) middleWareAuthenticate(next http.Handler) http.Handler {
|
func (service *middleWareService) middleWareAuthenticate(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var token string
|
if !service.authDisabled {
|
||||||
|
var token string
|
||||||
|
|
||||||
// Get token from the Authorization header
|
// Get token from the Authorization header
|
||||||
tokens, ok := r.Header["Authorization"]
|
tokens, ok := r.Header["Authorization"]
|
||||||
if ok && len(tokens) >= 1 {
|
if ok && len(tokens) >= 1 {
|
||||||
token = tokens[0]
|
token = tokens[0]
|
||||||
token = strings.TrimPrefix(token, "Bearer ")
|
token = strings.TrimPrefix(token, "Bearer ")
|
||||||
}
|
}
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
Error(w, portainer.ErrUnauthorized, http.StatusUnauthorized, nil)
|
Error(w, portainer.ErrUnauthorized, http.StatusUnauthorized, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := service.jwtService.VerifyToken(token)
|
err := service.jwtService.VerifyToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error(w, err, http.StatusUnauthorized, nil)
|
Error(w, err, http.StatusUnauthorized, nil)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|
|
@ -8,17 +8,19 @@ import (
|
||||||
|
|
||||||
// Server implements the portainer.Server interface
|
// Server implements the portainer.Server interface
|
||||||
type Server struct {
|
type Server struct {
|
||||||
BindAddress string
|
BindAddress string
|
||||||
AssetsPath string
|
AssetsPath string
|
||||||
UserService portainer.UserService
|
AuthDisabled bool
|
||||||
EndpointService portainer.EndpointService
|
EndpointManagement bool
|
||||||
CryptoService portainer.CryptoService
|
UserService portainer.UserService
|
||||||
JWTService portainer.JWTService
|
EndpointService portainer.EndpointService
|
||||||
FileService portainer.FileService
|
CryptoService portainer.CryptoService
|
||||||
Settings *portainer.Settings
|
JWTService portainer.JWTService
|
||||||
TemplatesURL string
|
FileService portainer.FileService
|
||||||
ActiveEndpoint *portainer.Endpoint
|
Settings *portainer.Settings
|
||||||
Handler *Handler
|
TemplatesURL string
|
||||||
|
ActiveEndpoint *portainer.Endpoint
|
||||||
|
Handler *Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) updateActiveEndpoint(endpoint *portainer.Endpoint) error {
|
func (server *Server) updateActiveEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
|
@ -40,13 +42,15 @@ func (server *Server) updateActiveEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
// Start starts the HTTP server
|
// Start starts the HTTP server
|
||||||
func (server *Server) Start() error {
|
func (server *Server) Start() error {
|
||||||
middleWareService := &middleWareService{
|
middleWareService := &middleWareService{
|
||||||
jwtService: server.JWTService,
|
jwtService: server.JWTService,
|
||||||
|
authDisabled: server.AuthDisabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
var authHandler = NewAuthHandler()
|
var authHandler = NewAuthHandler()
|
||||||
authHandler.UserService = server.UserService
|
authHandler.UserService = server.UserService
|
||||||
authHandler.CryptoService = server.CryptoService
|
authHandler.CryptoService = server.CryptoService
|
||||||
authHandler.JWTService = server.JWTService
|
authHandler.JWTService = server.JWTService
|
||||||
|
authHandler.authDisabled = server.AuthDisabled
|
||||||
var userHandler = NewUserHandler(middleWareService)
|
var userHandler = NewUserHandler(middleWareService)
|
||||||
userHandler.UserService = server.UserService
|
userHandler.UserService = server.UserService
|
||||||
userHandler.CryptoService = server.CryptoService
|
userHandler.CryptoService = server.CryptoService
|
||||||
|
@ -58,6 +62,7 @@ func (server *Server) Start() error {
|
||||||
var websocketHandler = NewWebSocketHandler()
|
var websocketHandler = NewWebSocketHandler()
|
||||||
// EndpointHandler requires a reference to the server to be able to update the active endpoint.
|
// EndpointHandler requires a reference to the server to be able to update the active endpoint.
|
||||||
var endpointHandler = NewEndpointHandler(middleWareService)
|
var endpointHandler = NewEndpointHandler(middleWareService)
|
||||||
|
endpointHandler.authorizeEndpointManagement = server.EndpointManagement
|
||||||
endpointHandler.EndpointService = server.EndpointService
|
endpointHandler.EndpointService = server.EndpointService
|
||||||
endpointHandler.FileService = server.FileService
|
endpointHandler.FileService = server.FileService
|
||||||
endpointHandler.server = server
|
endpointHandler.server = server
|
||||||
|
|
|
@ -13,23 +13,28 @@ type (
|
||||||
|
|
||||||
// CLIFlags represents the available flags on the CLI.
|
// CLIFlags represents the available flags on the CLI.
|
||||||
CLIFlags struct {
|
CLIFlags struct {
|
||||||
Addr *string
|
Addr *string
|
||||||
Assets *string
|
Assets *string
|
||||||
Data *string
|
Data *string
|
||||||
Endpoint *string
|
ExternalEndpoints *string
|
||||||
Labels *[]Pair
|
SyncInterval *string
|
||||||
Logo *string
|
Endpoint *string
|
||||||
Templates *string
|
Labels *[]Pair
|
||||||
TLSVerify *bool
|
Logo *string
|
||||||
TLSCacert *string
|
Templates *string
|
||||||
TLSCert *string
|
NoAuth *bool
|
||||||
TLSKey *string
|
TLSVerify *bool
|
||||||
|
TLSCacert *string
|
||||||
|
TLSCert *string
|
||||||
|
TLSKey *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings represents Portainer settings.
|
// Settings represents Portainer settings.
|
||||||
Settings struct {
|
Settings struct {
|
||||||
HiddenLabels []Pair `json:"hiddenLabels"`
|
HiddenLabels []Pair `json:"hiddenLabels"`
|
||||||
Logo string `json:"logo"`
|
Logo string `json:"logo"`
|
||||||
|
Authentication bool `json:"authentication"`
|
||||||
|
EndpointManagement bool `json:"endpointManagement"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User represent a user account.
|
// User represent a user account.
|
||||||
|
@ -95,6 +100,7 @@ type (
|
||||||
GetActive() (*Endpoint, error)
|
GetActive() (*Endpoint, error)
|
||||||
SetActive(endpoint *Endpoint) error
|
SetActive(endpoint *Endpoint) error
|
||||||
DeleteActive() error
|
DeleteActive() error
|
||||||
|
Synchronize(toCreate, toUpdate, toDelete []*Endpoint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// CryptoService represents a service for encrypting/hashing data.
|
// CryptoService represents a service for encrypting/hashing data.
|
||||||
|
@ -115,11 +121,16 @@ type (
|
||||||
GetPathForTLSFile(endpointID EndpointID, fileType TLSFileType) (string, error)
|
GetPathForTLSFile(endpointID EndpointID, fileType TLSFileType) (string, error)
|
||||||
DeleteTLSFiles(endpointID EndpointID) error
|
DeleteTLSFiles(endpointID EndpointID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EndpointWatcher represents a service to synchronize the endpoints via an external source.
|
||||||
|
EndpointWatcher interface {
|
||||||
|
WatchEndpointFile(endpointFilePath string) error
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// APIVersion is the version number of portainer API.
|
// APIVersion is the version number of portainer API.
|
||||||
APIVersion = "1.11.3"
|
APIVersion = "1.11.4"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
274
app/app.js
274
app/app.js
|
@ -1,5 +1,8 @@
|
||||||
|
angular.module('portainer.filters', []);
|
||||||
|
angular.module('portainer.rest', ['ngResource']);
|
||||||
|
angular.module('portainer.services', []);
|
||||||
|
angular.module('portainer.helpers', []);
|
||||||
angular.module('portainer', [
|
angular.module('portainer', [
|
||||||
'portainer.templates',
|
|
||||||
'ui.bootstrap',
|
'ui.bootstrap',
|
||||||
'ui.router',
|
'ui.router',
|
||||||
'ui.select',
|
'ui.select',
|
||||||
|
@ -9,9 +12,11 @@ angular.module('portainer', [
|
||||||
'angularUtils.directives.dirPagination',
|
'angularUtils.directives.dirPagination',
|
||||||
'LocalStorageModule',
|
'LocalStorageModule',
|
||||||
'angular-jwt',
|
'angular-jwt',
|
||||||
'portainer.services',
|
'portainer.templates',
|
||||||
'portainer.helpers',
|
|
||||||
'portainer.filters',
|
'portainer.filters',
|
||||||
|
'portainer.rest',
|
||||||
|
'portainer.helpers',
|
||||||
|
'portainer.services',
|
||||||
'auth',
|
'auth',
|
||||||
'dashboard',
|
'dashboard',
|
||||||
'container',
|
'container',
|
||||||
|
@ -19,29 +24,29 @@ angular.module('portainer', [
|
||||||
'containerLogs',
|
'containerLogs',
|
||||||
'containers',
|
'containers',
|
||||||
'createContainer',
|
'createContainer',
|
||||||
|
'createNetwork',
|
||||||
|
'createService',
|
||||||
|
'createVolume',
|
||||||
'docker',
|
'docker',
|
||||||
'endpoint',
|
'endpoint',
|
||||||
'endpointInit',
|
'endpointInit',
|
||||||
'endpoints',
|
'endpoints',
|
||||||
'events',
|
'events',
|
||||||
'images',
|
|
||||||
'image',
|
'image',
|
||||||
|
'images',
|
||||||
'main',
|
'main',
|
||||||
|
'network',
|
||||||
|
'networks',
|
||||||
|
'node',
|
||||||
'service',
|
'service',
|
||||||
'services',
|
'services',
|
||||||
'settings',
|
'settings',
|
||||||
'sidebar',
|
'sidebar',
|
||||||
'createService',
|
|
||||||
'stats',
|
'stats',
|
||||||
'swarm',
|
'swarm',
|
||||||
'network',
|
|
||||||
'networks',
|
|
||||||
'node',
|
|
||||||
'createNetwork',
|
|
||||||
'task',
|
'task',
|
||||||
'templates',
|
'templates',
|
||||||
'volumes',
|
'volumes'])
|
||||||
'createVolume'])
|
|
||||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider) {
|
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -62,124 +67,121 @@ angular.module('portainer', [
|
||||||
$urlRouterProvider.otherwise('/auth');
|
$urlRouterProvider.otherwise('/auth');
|
||||||
|
|
||||||
$stateProvider
|
$stateProvider
|
||||||
|
.state('root', {
|
||||||
|
abstract: true,
|
||||||
|
resolve: {
|
||||||
|
requiresLogin: ['StateManager', function (StateManager) {
|
||||||
|
var applicationState = StateManager.getState();
|
||||||
|
return applicationState.application.authentication;
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
.state('auth', {
|
.state('auth', {
|
||||||
url: '/auth',
|
parent: 'root',
|
||||||
|
url: '/auth',
|
||||||
params: {
|
params: {
|
||||||
logout: false,
|
logout: false,
|
||||||
error: ''
|
error: ''
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/auth/auth.html',
|
templateUrl: 'app/components/auth/auth.html',
|
||||||
controller: 'AuthenticationController'
|
controller: 'AuthenticationController'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
requiresLogin: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('containers', {
|
.state('containers', {
|
||||||
|
parent: 'root',
|
||||||
url: '/containers/',
|
url: '/containers/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/containers/containers.html',
|
templateUrl: 'app/components/containers/containers.html',
|
||||||
controller: 'ContainersController'
|
controller: 'ContainersController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('container', {
|
.state('container', {
|
||||||
url: "^/containers/:id",
|
url: "^/containers/:id",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/container/container.html',
|
templateUrl: 'app/components/container/container.html',
|
||||||
controller: 'ContainerController'
|
controller: 'ContainerController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('stats', {
|
.state('stats', {
|
||||||
url: "^/containers/:id/stats",
|
url: "^/containers/:id/stats",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/stats/stats.html',
|
templateUrl: 'app/components/stats/stats.html',
|
||||||
controller: 'StatsController'
|
controller: 'StatsController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('logs', {
|
.state('logs', {
|
||||||
url: "^/containers/:id/logs",
|
url: "^/containers/:id/logs",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/containerLogs/containerlogs.html',
|
templateUrl: 'app/components/containerLogs/containerlogs.html',
|
||||||
controller: 'ContainerLogsController'
|
controller: 'ContainerLogsController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('console', {
|
.state('console', {
|
||||||
url: "^/containers/:id/console",
|
url: "^/containers/:id/console",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/containerConsole/containerConsole.html',
|
templateUrl: 'app/components/containerConsole/containerConsole.html',
|
||||||
controller: 'ContainerConsoleController'
|
controller: 'ContainerConsoleController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('dashboard', {
|
.state('dashboard', {
|
||||||
|
parent: 'root',
|
||||||
url: '/dashboard',
|
url: '/dashboard',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/dashboard/dashboard.html',
|
templateUrl: 'app/components/dashboard/dashboard.html',
|
||||||
controller: 'DashboardController'
|
controller: 'DashboardController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions', {
|
.state('actions', {
|
||||||
abstract: true,
|
abstract: true,
|
||||||
url: "/actions",
|
url: "/actions",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
template: '<div ui-view="content"></div>'
|
template: '<div ui-view="content@"></div>'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
template: '<div ui-view="sidebar"></div>'
|
template: '<div ui-view="sidebar@"></div>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -187,344 +189,281 @@ angular.module('portainer', [
|
||||||
abstract: true,
|
abstract: true,
|
||||||
url: "/create",
|
url: "/create",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
template: '<div ui-view="content"></div>'
|
template: '<div ui-view="content@"></div>'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
template: '<div ui-view="sidebar"></div>'
|
template: '<div ui-view="sidebar@"></div>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions.create.container', {
|
.state('actions.create.container', {
|
||||||
url: "/container",
|
url: "/container",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||||
controller: 'CreateContainerController'
|
controller: 'CreateContainerController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions.create.network', {
|
.state('actions.create.network', {
|
||||||
url: "/network",
|
url: "/network",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/createNetwork/createnetwork.html',
|
templateUrl: 'app/components/createNetwork/createnetwork.html',
|
||||||
controller: 'CreateNetworkController'
|
controller: 'CreateNetworkController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions.create.service', {
|
.state('actions.create.service', {
|
||||||
url: "/service",
|
url: "/service",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/createService/createservice.html',
|
templateUrl: 'app/components/createService/createservice.html',
|
||||||
controller: 'CreateServiceController'
|
controller: 'CreateServiceController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions.create.volume', {
|
.state('actions.create.volume', {
|
||||||
url: "/volume",
|
url: "/volume",
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/createVolume/createvolume.html',
|
templateUrl: 'app/components/createVolume/createvolume.html',
|
||||||
controller: 'CreateVolumeController'
|
controller: 'CreateVolumeController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('docker', {
|
.state('docker', {
|
||||||
url: '/docker/',
|
url: '/docker/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/docker/docker.html',
|
templateUrl: 'app/components/docker/docker.html',
|
||||||
controller: 'DockerController'
|
controller: 'DockerController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('endpoints', {
|
.state('endpoints', {
|
||||||
url: '/endpoints/',
|
url: '/endpoints/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/endpoints/endpoints.html',
|
templateUrl: 'app/components/endpoints/endpoints.html',
|
||||||
controller: 'EndpointsController'
|
controller: 'EndpointsController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('endpoint', {
|
.state('endpoint', {
|
||||||
url: '^/endpoints/:id',
|
url: '^/endpoints/:id',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/endpoint/endpoint.html',
|
templateUrl: 'app/components/endpoint/endpoint.html',
|
||||||
controller: 'EndpointController'
|
controller: 'EndpointController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('endpointInit', {
|
.state('endpointInit', {
|
||||||
url: '/init/endpoint',
|
url: '/init/endpoint',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/endpointInit/endpointInit.html',
|
templateUrl: 'app/components/endpointInit/endpointInit.html',
|
||||||
controller: 'EndpointInitController'
|
controller: 'EndpointInitController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('events', {
|
.state('events', {
|
||||||
url: '/events/',
|
url: '/events/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/events/events.html',
|
templateUrl: 'app/components/events/events.html',
|
||||||
controller: 'EventsController'
|
controller: 'EventsController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('images', {
|
.state('images', {
|
||||||
url: '/images/',
|
url: '/images/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/images/images.html',
|
templateUrl: 'app/components/images/images.html',
|
||||||
controller: 'ImagesController'
|
controller: 'ImagesController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('image', {
|
.state('image', {
|
||||||
url: '^/images/:id/',
|
url: '^/images/:id/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/image/image.html',
|
templateUrl: 'app/components/image/image.html',
|
||||||
controller: 'ImageController'
|
controller: 'ImageController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('networks', {
|
.state('networks', {
|
||||||
url: '/networks/',
|
url: '/networks/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/networks/networks.html',
|
templateUrl: 'app/components/networks/networks.html',
|
||||||
controller: 'NetworksController'
|
controller: 'NetworksController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('network', {
|
.state('network', {
|
||||||
url: '^/networks/:id/',
|
url: '^/networks/:id/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/network/network.html',
|
templateUrl: 'app/components/network/network.html',
|
||||||
controller: 'NetworkController'
|
controller: 'NetworkController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('node', {
|
.state('node', {
|
||||||
url: '^/nodes/:id/',
|
url: '^/nodes/:id/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/node/node.html',
|
templateUrl: 'app/components/node/node.html',
|
||||||
controller: 'NodeController'
|
controller: 'NodeController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('services', {
|
.state('services', {
|
||||||
url: '/services/',
|
url: '/services/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/services/services.html',
|
templateUrl: 'app/components/services/services.html',
|
||||||
controller: 'ServicesController'
|
controller: 'ServicesController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('service', {
|
.state('service', {
|
||||||
url: '^/service/:id/',
|
url: '^/service/:id/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/service/service.html',
|
templateUrl: 'app/components/service/service.html',
|
||||||
controller: 'ServiceController'
|
controller: 'ServiceController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('settings', {
|
.state('settings', {
|
||||||
url: '/settings/',
|
url: '/settings/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/settings/settings.html',
|
templateUrl: 'app/components/settings/settings.html',
|
||||||
controller: 'SettingsController'
|
controller: 'SettingsController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('task', {
|
.state('task', {
|
||||||
url: '^/task/:id',
|
url: '^/task/:id',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/task/task.html',
|
templateUrl: 'app/components/task/task.html',
|
||||||
controller: 'TaskController'
|
controller: 'TaskController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('templates', {
|
.state('templates', {
|
||||||
url: '/templates/',
|
url: '/templates/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/templates/templates.html',
|
templateUrl: 'app/components/templates/templates.html',
|
||||||
controller: 'TemplatesController'
|
controller: 'TemplatesController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('volumes', {
|
.state('volumes', {
|
||||||
url: '/volumes/',
|
url: '/volumes/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/volumes/volumes.html',
|
templateUrl: 'app/components/volumes/volumes.html',
|
||||||
controller: 'VolumesController'
|
controller: 'VolumesController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('swarm', {
|
.state('swarm', {
|
||||||
url: '/swarm/',
|
url: '/swarm/',
|
||||||
views: {
|
views: {
|
||||||
"content": {
|
"content@": {
|
||||||
templateUrl: 'app/components/swarm/swarm.html',
|
templateUrl: 'app/components/swarm/swarm.html',
|
||||||
controller: 'SwarmController'
|
controller: 'SwarmController'
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar@": {
|
||||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
controller: 'SidebarController'
|
controller: 'SidebarController'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
requiresLogin: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -545,18 +484,21 @@ angular.module('portainer', [
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}])
|
}])
|
||||||
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'StateManager', function ($rootScope, $state, Authentication, authManager, StateManager) {
|
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'StateManager', 'Messages', function ($rootScope, $state, Authentication, authManager, StateManager, Messages) {
|
||||||
authManager.checkAuthOnRefresh();
|
StateManager.initialize().then(function success(state) {
|
||||||
authManager.redirectWhenUnauthenticated();
|
if (state.application.authentication) {
|
||||||
|
authManager.checkAuthOnRefresh();
|
||||||
Authentication.init();
|
authManager.redirectWhenUnauthenticated();
|
||||||
StateManager.init();
|
Authentication.init();
|
||||||
|
$rootScope.$on('tokenHasExpired', function($state) {
|
||||||
|
$state.go('auth', {error: 'Your session has expired'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function error(err) {
|
||||||
|
Messages.error("Failure", err, 'Unable to retrieve application settings');
|
||||||
|
});
|
||||||
|
|
||||||
$rootScope.$state = $state;
|
$rootScope.$state = $state;
|
||||||
|
|
||||||
$rootScope.$on('tokenHasExpired', function($state) {
|
|
||||||
$state.go('auth', {error: 'Your session has expired'});
|
|
||||||
});
|
|
||||||
}])
|
}])
|
||||||
// This is your docker url that the api will use to make requests
|
// This is your docker url that the api will use to make requests
|
||||||
// You need to set this to the api endpoint without the port i.e. http://192.168.1.9
|
// You need to set this to the api endpoint without the port i.e. http://192.168.1.9
|
||||||
|
@ -568,4 +510,4 @@ angular.module('portainer', [
|
||||||
.constant('ENDPOINTS_ENDPOINT', 'api/endpoints')
|
.constant('ENDPOINTS_ENDPOINT', 'api/endpoints')
|
||||||
.constant('TEMPLATES_ENDPOINT', 'api/templates')
|
.constant('TEMPLATES_ENDPOINT', 'api/templates')
|
||||||
.constant('PAGINATION_MAX_ITEMS', 10)
|
.constant('PAGINATION_MAX_ITEMS', 10)
|
||||||
.constant('UI_VERSION', 'v1.11.3');
|
.constant('UI_VERSION', 'v1.11.4');
|
||||||
|
|
|
@ -13,6 +13,23 @@ function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Au
|
||||||
error: false
|
error: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!$scope.applicationState.application.authentication) {
|
||||||
|
EndpointService.getActive().then(function success(data) {
|
||||||
|
StateManager.updateEndpointState(true)
|
||||||
|
.then(function success() {
|
||||||
|
$state.go('dashboard');
|
||||||
|
}, function error(err) {
|
||||||
|
Messages.error("Failure", err, 'Unable to connect to the Docker endpoint');
|
||||||
|
});
|
||||||
|
}, function error(err) {
|
||||||
|
if (err.status === 404) {
|
||||||
|
$state.go('endpointInit');
|
||||||
|
} else {
|
||||||
|
Messages.error("Failure", err, 'Unable to verify Docker endpoint existence');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ($stateParams.logout) {
|
if ($stateParams.logout) {
|
||||||
Authentication.logout();
|
Authentication.logout();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
angular.module('endpoint', [])
|
angular.module('endpoint', [])
|
||||||
.controller('EndpointController', ['$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'Messages',
|
.controller('EndpointController', ['$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'Messages',
|
||||||
function ($scope, $state, $stateParams, $filter, EndpointService, Messages) {
|
function ($scope, $state, $stateParams, $filter, EndpointService, Messages) {
|
||||||
|
|
||||||
|
if (!$scope.applicationState.application.endpointManagement) {
|
||||||
|
$state.go('endpoints');
|
||||||
|
}
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
error: '',
|
error: '',
|
||||||
uploadInProgress: false
|
uploadInProgress: false
|
||||||
|
|
|
@ -8,7 +8,19 @@
|
||||||
<rd-header-content>Endpoint management</rd-header-content>
|
<rd-header-content>Endpoint management</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row" ng-if="!applicationState.application.endpointManagement">
|
||||||
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-exclamation-triangle" title="Endpoint management is not available">
|
||||||
|
</rd-widget-header>
|
||||||
|
<rd-widget-body>
|
||||||
|
<span class="small text-muted">Portainer has been started using the <code>--external-endpoints</code> flag. Endpoint management via the UI is disabled.</span>
|
||||||
|
</rd-wigdet-body>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-if="applicationState.application.endpointManagement">
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-header icon="fa-plus" title="Add a new endpoint">
|
<rd-widget-header icon="fa-plus" title="Add a new endpoint">
|
||||||
|
@ -113,7 +125,7 @@
|
||||||
</div>
|
</div>
|
||||||
</rd-widget-header>
|
</rd-widget-header>
|
||||||
<rd-widget-taskbar classes="col-lg-12">
|
<rd-widget-taskbar classes="col-lg-12">
|
||||||
<div class="pull-left">
|
<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>
|
<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>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
@ -125,7 +137,7 @@
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th ng-if="applicationState.application.endpointManagement"></th>
|
||||||
<th>
|
<th>
|
||||||
<a ui-sref="endpoints" ng-click="order('Name')">
|
<a ui-sref="endpoints" ng-click="order('Name')">
|
||||||
Name
|
Name
|
||||||
|
@ -147,16 +159,16 @@
|
||||||
<span ng-show="sortType == 'TLS' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
<span ng-show="sortType == 'TLS' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th></th>
|
<th ng-if="applicationState.application.endpointManagement"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr dir-paginate="endpoint in (state.filteredEndpoints = (endpoints | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
<tr dir-paginate="endpoint in (state.filteredEndpoints = (endpoints | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||||
<td><input type="checkbox" ng-model="endpoint.Checked" ng-change="selectItem(endpoint)" /></td>
|
<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><i class="fa fa-star" aria-hidden="true" ng-if="endpoint.Id === activeEndpoint.Id"></i> {{ endpoint.Name }}</td>
|
||||||
<td>{{ endpoint.URL | stripprotocol }}</td>
|
<td>{{ endpoint.URL | stripprotocol }}</td>
|
||||||
<td><i class="fa fa-shield" aria-hidden="true" ng-if="endpoint.TLS"></i></td>
|
<td><i class="fa fa-shield" aria-hidden="true" ng-if="endpoint.TLS"></i></td>
|
||||||
<td>
|
<td ng-if="applicationState.application.endpointManagement">
|
||||||
<span ng-if="endpoint.Id !== activeEndpoint.Id">
|
<span ng-if="endpoint.Id !== activeEndpoint.Id">
|
||||||
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<a ui-sref="docker" ui-sref-active="active">Docker <span class="menu-icon fa fa-th"></span></a>
|
<a ui-sref="docker" ui-sref-active="active">Docker <span class="menu-icon fa fa-th"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-title"><span>Portainer settings</span></li>
|
<li class="sidebar-title"><span>Portainer settings</span></li>
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list" ng-if="applicationState.application.authentication">
|
||||||
<a ui-sref="settings" ui-sref-active="active">Password <span class="menu-icon fa fa-lock"></span></a>
|
<a ui-sref="settings" ui-sref-active="active">Password <span class="menu-icon fa fa-lock"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list">
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
angular
|
angular
|
||||||
.module('portainer')
|
.module('portainer')
|
||||||
.directive('rdHeaderContent', function rdHeaderContent() {
|
.directive('rdHeaderContent', ['Authentication', function rdHeaderContent(Authentication) {
|
||||||
var directive = {
|
var directive = {
|
||||||
requires: '^rdHeader',
|
requires: '^rdHeader',
|
||||||
transclude: true,
|
transclude: true,
|
||||||
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right"><a ui-sref="auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u>log out <i class="fa fa-sign-out" aria-hidden="true"></i></u></a></div></div>',
|
link: function (scope, iElement, iAttrs) {
|
||||||
|
scope.username = Authentication.getCredentials().username;
|
||||||
|
},
|
||||||
|
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u>log out <i class="fa fa-sign-out" aria-hidden="true"></i></u></a></div></div>',
|
||||||
restrict: 'E'
|
restrict: 'E'
|
||||||
};
|
};
|
||||||
return directive;
|
return directive;
|
||||||
});
|
}]);
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
angular
|
angular
|
||||||
.module('portainer')
|
.module('portainer')
|
||||||
.directive('rdHeaderTitle', ['$rootScope', function rdHeaderTitle($rootScope) {
|
.directive('rdHeaderTitle', ['Authentication', function rdHeaderTitle(Authentication) {
|
||||||
var directive = {
|
var directive = {
|
||||||
requires: '^rdHeader',
|
requires: '^rdHeader',
|
||||||
scope: {
|
scope: {
|
||||||
title: '@'
|
title: '@'
|
||||||
},
|
},
|
||||||
link: function (scope, iElement, iAttrs) {
|
link: function (scope, iElement, iAttrs) {
|
||||||
scope.username = $rootScope.username;
|
scope.username = Authentication.getCredentials().username;
|
||||||
},
|
},
|
||||||
transclude: true,
|
transclude: true,
|
||||||
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box"><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></div>',
|
||||||
restrict: 'E'
|
restrict: 'E'
|
||||||
};
|
};
|
||||||
return directive;
|
return directive;
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('ContainerHelper', [function ContainerHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
hideContainers: function(containers, containersToHideLabels) {
|
||||||
|
return containers.filter(function (container) {
|
||||||
|
var filterContainer = false;
|
||||||
|
containersToHideLabels.forEach(function(label, index) {
|
||||||
|
if (_.has(container.Labels, label.name) &&
|
||||||
|
container.Labels[label.name] === label.value) {
|
||||||
|
filterContainer = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!filterContainer) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,30 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('ImageHelper', [function ImageHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
createImageConfigForCommit: function(imageName, registry) {
|
||||||
|
var imageNameAndTag = imageName.split(':');
|
||||||
|
var image = imageNameAndTag[0];
|
||||||
|
if (registry) {
|
||||||
|
image = registry + '/' + imageNameAndTag[0];
|
||||||
|
}
|
||||||
|
var imageConfig = {
|
||||||
|
repo: image,
|
||||||
|
tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
|
||||||
|
};
|
||||||
|
return imageConfig;
|
||||||
|
},
|
||||||
|
createImageConfigForContainer: function (imageName, registry) {
|
||||||
|
var imageNameAndTag = imageName.split(':');
|
||||||
|
var image = imageNameAndTag[0];
|
||||||
|
if (registry) {
|
||||||
|
image = registry + '/' + imageNameAndTag[0];
|
||||||
|
}
|
||||||
|
var imageConfig = {
|
||||||
|
fromImage: image,
|
||||||
|
tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
|
||||||
|
};
|
||||||
|
return imageConfig;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,32 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('InfoHelper', [function InfoHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
determineEndpointMode: function(info) {
|
||||||
|
var mode = {
|
||||||
|
provider: '',
|
||||||
|
role: ''
|
||||||
|
};
|
||||||
|
if (_.startsWith(info.ServerVersion, 'swarm')) {
|
||||||
|
mode.provider = "DOCKER_SWARM";
|
||||||
|
if (info.SystemStatus[0][1] === 'primary') {
|
||||||
|
mode.role = "PRIMARY";
|
||||||
|
} else {
|
||||||
|
mode.role = "REPLICA";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
|
||||||
|
mode.provider = "DOCKER_STANDALONE";
|
||||||
|
} else {
|
||||||
|
mode.provider = "DOCKER_SWARM_MODE";
|
||||||
|
if (info.Swarm.ControlAvailable) {
|
||||||
|
mode.role = "MANAGER";
|
||||||
|
} else {
|
||||||
|
mode.role = "WORKER";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,23 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('LabelHelper', [function LabelHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
fromLabelHashToKeyValue: function(labels) {
|
||||||
|
if (labels) {
|
||||||
|
return Object.keys(labels).map(function(key) {
|
||||||
|
return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
fromKeyValueToLabelHash: function(labelKV) {
|
||||||
|
var labels = {};
|
||||||
|
if (labelKV) {
|
||||||
|
labelKV.forEach(function(label) {
|
||||||
|
labels[label.key] = label.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,14 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('NodeHelper', [function NodeHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
nodeToConfig: function(node) {
|
||||||
|
return {
|
||||||
|
Name: node.Spec.Name,
|
||||||
|
Role: node.Spec.Role,
|
||||||
|
Labels: node.Spec.Labels,
|
||||||
|
Availability: node.Spec.Availability
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,17 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('ServiceHelper', [function ServiceHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
serviceToConfig: function(service) {
|
||||||
|
return {
|
||||||
|
Name: service.Spec.Name,
|
||||||
|
Labels: service.Spec.Labels,
|
||||||
|
TaskTemplate: service.Spec.TaskTemplate,
|
||||||
|
Mode: service.Spec.Mode,
|
||||||
|
UpdateConfig: service.Spec.UpdateConfig,
|
||||||
|
Networks: service.Spec.Networks,
|
||||||
|
EndpointSpec: service.Spec.EndpointSpec
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,40 @@
|
||||||
|
angular.module('portainer.helpers')
|
||||||
|
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
getPortBindings: function(ports) {
|
||||||
|
var bindings = [];
|
||||||
|
ports.forEach(function (port) {
|
||||||
|
var portAndProtocol = _.split(port, '/');
|
||||||
|
var binding = {
|
||||||
|
containerPort: portAndProtocol[0],
|
||||||
|
protocol: portAndProtocol[1]
|
||||||
|
};
|
||||||
|
bindings.push(binding);
|
||||||
|
});
|
||||||
|
return bindings;
|
||||||
|
},
|
||||||
|
//Not used atm, may prove useful later
|
||||||
|
getVolumeBindings: function(volumes) {
|
||||||
|
var bindings = [];
|
||||||
|
volumes.forEach(function (volume) {
|
||||||
|
bindings.push({ containerPath: volume });
|
||||||
|
});
|
||||||
|
return bindings;
|
||||||
|
},
|
||||||
|
//Not used atm, may prove useful later
|
||||||
|
getEnvBindings: function(env) {
|
||||||
|
var bindings = [];
|
||||||
|
env.forEach(function (envvar) {
|
||||||
|
var binding = {
|
||||||
|
name: envvar.name
|
||||||
|
};
|
||||||
|
if (envvar.set) {
|
||||||
|
binding.value = envvar.set;
|
||||||
|
}
|
||||||
|
bindings.push(binding);
|
||||||
|
});
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,20 @@
|
||||||
|
function ContainerViewModel(data) {
|
||||||
|
this.Id = data.Id;
|
||||||
|
this.Status = data.Status;
|
||||||
|
this.State = data.State;
|
||||||
|
this.Names = data.Names;
|
||||||
|
// Unavailable in Docker < 1.10
|
||||||
|
if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
|
||||||
|
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||||
|
}
|
||||||
|
this.Image = data.Image;
|
||||||
|
this.Command = data.Command;
|
||||||
|
this.Checked = false;
|
||||||
|
this.Ports = [];
|
||||||
|
for (var i = 0; i < data.Ports.length; ++i) {
|
||||||
|
var p = data.Ports[i];
|
||||||
|
if (p.PublicPort) {
|
||||||
|
this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
function createEventDetails(event) {
|
||||||
|
var eventAttr = event.Actor.Attributes;
|
||||||
|
var details = '';
|
||||||
|
switch (event.Type) {
|
||||||
|
case 'container':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'stop':
|
||||||
|
details = 'Container ' + eventAttr.name + ' stopped';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Container ' + eventAttr.name + ' deleted';
|
||||||
|
break;
|
||||||
|
case 'create':
|
||||||
|
details = 'Container ' + eventAttr.name + ' created';
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
details = 'Container ' + eventAttr.name + ' started';
|
||||||
|
break;
|
||||||
|
case 'kill':
|
||||||
|
details = 'Container ' + eventAttr.name + ' killed';
|
||||||
|
break;
|
||||||
|
case 'die':
|
||||||
|
details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
|
||||||
|
break;
|
||||||
|
case 'commit':
|
||||||
|
details = 'Container ' + eventAttr.name + ' committed';
|
||||||
|
break;
|
||||||
|
case 'restart':
|
||||||
|
details = 'Container ' + eventAttr.name + ' restarted';
|
||||||
|
break;
|
||||||
|
case 'pause':
|
||||||
|
details = 'Container ' + eventAttr.name + ' paused';
|
||||||
|
break;
|
||||||
|
case 'unpause':
|
||||||
|
details = 'Container ' + eventAttr.name + ' unpaused';
|
||||||
|
break;
|
||||||
|
case 'attach':
|
||||||
|
details = 'Container ' + eventAttr.name + ' attached';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (event.Action.indexOf('exec_create') === 0) {
|
||||||
|
details = 'Exec instance created';
|
||||||
|
} else if (event.Action.indexOf('exec_start') === 0) {
|
||||||
|
details = 'Exec instance started';
|
||||||
|
} else {
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'delete':
|
||||||
|
details = 'Image deleted';
|
||||||
|
break;
|
||||||
|
case 'tag':
|
||||||
|
details = 'New tag created for ' + eventAttr.name;
|
||||||
|
break;
|
||||||
|
case 'untag':
|
||||||
|
details = 'Image untagged';
|
||||||
|
break;
|
||||||
|
case 'pull':
|
||||||
|
details = 'Image ' + event.Actor.ID + ' pulled';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'network':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'create':
|
||||||
|
details = 'Network ' + eventAttr.name + ' created';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Network ' + eventAttr.name + ' deleted';
|
||||||
|
break;
|
||||||
|
case 'connect':
|
||||||
|
details = 'Container connected to ' + eventAttr.name + ' network';
|
||||||
|
break;
|
||||||
|
case 'disconnect':
|
||||||
|
details = 'Container disconnected from ' + eventAttr.name + ' network';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'volume':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'create':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' created';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' deleted';
|
||||||
|
break;
|
||||||
|
case 'mount':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' mounted';
|
||||||
|
break;
|
||||||
|
case 'unmount':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' unmounted';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EventViewModel(data) {
|
||||||
|
// Type, Action, Actor unavailable in Docker < 1.10
|
||||||
|
this.Time = data.time;
|
||||||
|
if (data.Type) {
|
||||||
|
this.Type = data.Type;
|
||||||
|
this.Details = createEventDetails(data);
|
||||||
|
} else {
|
||||||
|
this.Type = data.status;
|
||||||
|
this.Details = data.from;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
function ImageViewModel(data) {
|
||||||
|
this.Id = data.Id;
|
||||||
|
this.Tag = data.Tag;
|
||||||
|
this.Repository = data.Repository;
|
||||||
|
this.Created = data.Created;
|
||||||
|
this.Checked = false;
|
||||||
|
this.RepoTags = data.RepoTags;
|
||||||
|
this.VirtualSize = data.VirtualSize;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
function NodeViewModel(data) {
|
||||||
|
this.Model = data;
|
||||||
|
this.Id = data.ID;
|
||||||
|
this.Version = data.Version.Index;
|
||||||
|
this.Name = data.Spec.Name;
|
||||||
|
this.Role = data.Spec.Role;
|
||||||
|
this.CreatedAt = data.CreatedAt;
|
||||||
|
this.UpdatedAt = data.UpdatedAt;
|
||||||
|
this.Availability = data.Spec.Availability;
|
||||||
|
|
||||||
|
var labels = data.Spec.Labels;
|
||||||
|
if (labels) {
|
||||||
|
this.Labels = Object.keys(labels).map(function(key) {
|
||||||
|
return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.Labels = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Hostname = data.Description.Hostname;
|
||||||
|
this.PlatformArchitecture = data.Description.Platform.Architecture;
|
||||||
|
this.PlatformOS = data.Description.Platform.OS;
|
||||||
|
this.CPUs = data.Description.Resources.NanoCPUs;
|
||||||
|
this.Memory = data.Description.Resources.MemoryBytes;
|
||||||
|
this.EngineVersion = data.Description.Engine.EngineVersion;
|
||||||
|
this.EngineLabels = data.Description.Engine.Labels;
|
||||||
|
this.Plugins = data.Description.Engine.Plugins;
|
||||||
|
this.Status = data.Status.State;
|
||||||
|
|
||||||
|
if (data.ManagerStatus) {
|
||||||
|
this.Leader = data.ManagerStatus.Leader;
|
||||||
|
this.Reachability = data.ManagerStatus.Reachability;
|
||||||
|
this.ManagerAddr = data.ManagerStatus.Addr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
function ServiceViewModel(data) {
|
||||||
|
this.Model = data;
|
||||||
|
this.Id = data.ID;
|
||||||
|
this.Name = data.Spec.Name;
|
||||||
|
this.Image = data.Spec.TaskTemplate.ContainerSpec.Image;
|
||||||
|
this.Version = data.Version.Index;
|
||||||
|
if (data.Spec.Mode.Replicated) {
|
||||||
|
this.Mode = 'replicated' ;
|
||||||
|
this.Replicas = data.Spec.Mode.Replicated.Replicas;
|
||||||
|
} else {
|
||||||
|
this.Mode = 'global';
|
||||||
|
}
|
||||||
|
this.Labels = data.Spec.Labels;
|
||||||
|
if (data.Spec.TaskTemplate.ContainerSpec) {
|
||||||
|
this.ContainerLabels = data.Spec.TaskTemplate.ContainerSpec.Labels;
|
||||||
|
}
|
||||||
|
if (data.Spec.TaskTemplate.ContainerSpec.Env) {
|
||||||
|
this.Env = data.Spec.TaskTemplate.ContainerSpec.Env;
|
||||||
|
}
|
||||||
|
if (data.Endpoint.Ports) {
|
||||||
|
this.Ports = data.Endpoint.Ports;
|
||||||
|
}
|
||||||
|
if (data.Spec.UpdateConfig) {
|
||||||
|
this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1;
|
||||||
|
this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0;
|
||||||
|
this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause';
|
||||||
|
} else {
|
||||||
|
this.UpdateParallelism = 1;
|
||||||
|
this.UpdateDelay = 0;
|
||||||
|
this.UpdateFailureAction = 'pause';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Checked = false;
|
||||||
|
this.Scale = false;
|
||||||
|
this.EditName = false;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
function TaskViewModel(data, node_data) {
|
||||||
|
this.Id = data.ID;
|
||||||
|
this.Created = data.CreatedAt;
|
||||||
|
this.Updated = data.UpdatedAt;
|
||||||
|
this.Slot = data.Slot;
|
||||||
|
this.Status = data.Status.State;
|
||||||
|
this.Image = data.Spec.ContainerSpec ? data.Spec.ContainerSpec.Image : '';
|
||||||
|
if (node_data) {
|
||||||
|
for (var i = 0; i < node_data.length; ++i) {
|
||||||
|
if (data.NodeID === node_data[i].ID) {
|
||||||
|
this.Node = node_data[i].Description.Hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Auth', ['$resource', 'AUTH_ENDPOINT', function AuthFactory($resource, AUTH_ENDPOINT) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(AUTH_ENDPOINT, {}, {
|
||||||
|
login: {
|
||||||
|
method: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,4 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Config', ['$resource', 'CONFIG_ENDPOINT', function ConfigFactory($resource, CONFIG_ENDPOINT) {
|
||||||
|
return $resource(CONFIG_ENDPOINT).get();
|
||||||
|
}]);
|
|
@ -0,0 +1,37 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Container', ['$resource', 'Settings', function ContainerFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/containers/:id/:action', {
|
||||||
|
name: '@name'
|
||||||
|
}, {
|
||||||
|
query: {method: 'GET', params: {all: 0, action: 'json', filters: '@filters' }, isArray: true},
|
||||||
|
get: {method: 'GET', params: {action: 'json'}},
|
||||||
|
stop: {method: 'POST', params: {id: '@id', t: 5, action: 'stop'}},
|
||||||
|
restart: {method: 'POST', params: {id: '@id', t: 5, action: 'restart'}},
|
||||||
|
kill: {method: 'POST', params: {id: '@id', action: 'kill'}},
|
||||||
|
pause: {method: 'POST', params: {id: '@id', action: 'pause'}},
|
||||||
|
unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}},
|
||||||
|
changes: {method: 'GET', params: {action: 'changes'}, isArray: true},
|
||||||
|
stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000},
|
||||||
|
start: {
|
||||||
|
method: 'POST', params: {id: '@id', action: 'start'},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
method: 'POST', params: {action: 'create'},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
method: 'DELETE', params: {id: '@id', v: 0},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
},
|
||||||
|
rename: {
|
||||||
|
method: 'POST', params: {id: '@id', action: 'rename', name: '@name'},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
},
|
||||||
|
exec: {
|
||||||
|
method: 'POST', params: {id: '@id', action: 'exec'},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,7 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('ContainerCommit', ['$resource', 'Settings', function ContainerCommitFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/commit', {}, {
|
||||||
|
commit: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,20 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('ContainerLogs', ['$http', 'Settings', function ContainerLogsFactory($http, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
get: function (id, params, callback) {
|
||||||
|
$http({
|
||||||
|
method: 'GET',
|
||||||
|
url: Settings.url + '/containers/' + id + '/logs',
|
||||||
|
params: {
|
||||||
|
'stdout': params.stdout || 0,
|
||||||
|
'stderr': params.stderr || 0,
|
||||||
|
'timestamps': params.timestamps || 0,
|
||||||
|
'tail': params.tail || 'all'
|
||||||
|
}
|
||||||
|
}).success(callback).error(function (data, status, headers, config) {
|
||||||
|
console.log(error, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,15 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('ContainerTop', ['$http', 'Settings', function ($http, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
get: function (id, params, callback, errorCallback) {
|
||||||
|
$http({
|
||||||
|
method: 'GET',
|
||||||
|
url: Settings.url + '/containers/' + id + '/top',
|
||||||
|
params: {
|
||||||
|
ps_args: params.ps_args
|
||||||
|
}
|
||||||
|
}).success(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,13 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Endpoints', ['$resource', 'ENDPOINTS_ENDPOINT', function EndpointsFactory($resource, ENDPOINTS_ENDPOINT) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(ENDPOINTS_ENDPOINT + '/:id/:action', {}, {
|
||||||
|
create: { method: 'POST' },
|
||||||
|
query: { method: 'GET', isArray: true },
|
||||||
|
get: { method: 'GET', params: { id: '@id' } },
|
||||||
|
update: { method: 'PUT', params: { id: '@id' } },
|
||||||
|
remove: { method: 'DELETE', params: { id: '@id'} },
|
||||||
|
getActiveEndpoint: { method: 'GET', params: { id: '0' } },
|
||||||
|
setActiveEndpoint: { method: 'POST', params: { id: '@id', action: 'active' } }
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,10 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/events', {}, {
|
||||||
|
query: {
|
||||||
|
method: 'GET', params: {since: '@since', until: '@until'},
|
||||||
|
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,10 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Exec', ['$resource', 'Settings', function ExecFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/exec/:id/:action', {}, {
|
||||||
|
resize: {
|
||||||
|
method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'},
|
||||||
|
transformResponse: genericHandler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,25 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Image', ['$resource', 'Settings', function ImageFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/images/:id/:action', {}, {
|
||||||
|
query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true},
|
||||||
|
get: {method: 'GET', params: {action: 'json'}},
|
||||||
|
search: {method: 'GET', params: {action: 'search'}},
|
||||||
|
history: {method: 'GET', params: {action: 'history'}, isArray: true},
|
||||||
|
insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
|
||||||
|
tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}},
|
||||||
|
inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
|
||||||
|
push: {
|
||||||
|
method: 'POST', params: {action: 'push', id: '@tag'},
|
||||||
|
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
|
||||||
|
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
method: 'DELETE', params: {id: '@id'},
|
||||||
|
isArray: true, transformResponse: deleteImageHandler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,5 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/info', {});
|
||||||
|
}]);
|
|
@ -0,0 +1,12 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
|
||||||
|
query: {method: 'GET', isArray: true},
|
||||||
|
get: {method: 'GET'},
|
||||||
|
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
|
||||||
|
remove: { method: 'DELETE', transformResponse: genericHandler },
|
||||||
|
connect: {method: 'POST', params: {action: 'connect'}},
|
||||||
|
disconnect: {method: 'POST', params: {action: 'disconnect'}}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,10 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/nodes/:id/:action', {}, {
|
||||||
|
query: {method: 'GET', isArray: true},
|
||||||
|
get: {method: 'GET', params: {id: '@id'}},
|
||||||
|
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
||||||
|
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,11 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Service', ['$resource', 'Settings', function ServiceFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/services/:id/:action', {}, {
|
||||||
|
get: { method: 'GET', params: {id: '@id'} },
|
||||||
|
query: { method: 'GET', isArray: true },
|
||||||
|
create: { method: 'POST', params: {action: 'create'} },
|
||||||
|
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
||||||
|
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,7 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/swarm', {}, {
|
||||||
|
get: {method: 'GET'}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,8 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Task', ['$resource', 'Settings', function TaskFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/tasks/:id', {}, {
|
||||||
|
get: { method: 'GET', params: {id: '@id'} },
|
||||||
|
query: { method: 'GET', isArray: true, params: {filters: '@filters'} }
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,6 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Templates', ['$resource', 'TEMPLATES_ENDPOINT', function TemplatesFactory($resource, TEMPLATES_ENDPOINT) {
|
||||||
|
return $resource(TEMPLATES_ENDPOINT, {}, {
|
||||||
|
get: {method: 'GET', isArray: true}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,12 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Users', ['$resource', 'USERS_ENDPOINT', function UsersFactory($resource, USERS_ENDPOINT) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(USERS_ENDPOINT + '/:username/:action', {}, {
|
||||||
|
create: { method: 'POST' },
|
||||||
|
get: { method: 'GET', params: { username: '@username' } },
|
||||||
|
update: { method: 'PUT', params: { username: '@username' } },
|
||||||
|
checkPassword: { method: 'POST', params: { username: '@username', action: 'passwd' } },
|
||||||
|
checkAdminUser: { method: 'GET', params: { username: 'admin', action: 'check' } },
|
||||||
|
initAdminUser: { method: 'POST', params: { username: 'admin', action: 'init' } }
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,5 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/version', {});
|
||||||
|
}]);
|
|
@ -0,0 +1,12 @@
|
||||||
|
angular.module('portainer.rest')
|
||||||
|
.factory('Volume', ['$resource', 'Settings', function VolumeFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return $resource(Settings.url + '/volumes/:name/:action', {name: '@name'}, {
|
||||||
|
query: {method: 'GET'},
|
||||||
|
get: {method: 'GET'},
|
||||||
|
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
|
||||||
|
remove: {
|
||||||
|
method: 'DELETE', transformResponse: genericHandler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -0,0 +1,38 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('Authentication', ['$q', 'Auth', 'jwtHelper', 'LocalStorage', 'StateManager', function AuthenticationFactory($q, Auth, jwtHelper, LocalStorage, StateManager) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var credentials = {};
|
||||||
|
return {
|
||||||
|
init: function() {
|
||||||
|
var jwt = LocalStorage.getJWT();
|
||||||
|
if (jwt) {
|
||||||
|
var tokenPayload = jwtHelper.decodeToken(jwt);
|
||||||
|
credentials.username = tokenPayload.username;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
login: function(username, password) {
|
||||||
|
return $q(function (resolve, reject) {
|
||||||
|
Auth.login({username: username, password: password}).$promise
|
||||||
|
.then(function(data) {
|
||||||
|
LocalStorage.storeJWT(data.jwt);
|
||||||
|
credentials.username = username;
|
||||||
|
resolve();
|
||||||
|
}, function() {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout: function() {
|
||||||
|
StateManager.clean();
|
||||||
|
LocalStorage.clean();
|
||||||
|
},
|
||||||
|
isAuthenticated: function() {
|
||||||
|
var jwt = LocalStorage.getJWT();
|
||||||
|
return jwt && !jwtHelper.isTokenExpired(jwt);
|
||||||
|
},
|
||||||
|
getCredentials: function() {
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,84 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
getActive: function() {
|
||||||
|
return Endpoints.getActiveEndpoint().$promise;
|
||||||
|
},
|
||||||
|
setActive: function(endpointID) {
|
||||||
|
return Endpoints.setActiveEndpoint({id: endpointID}).$promise;
|
||||||
|
},
|
||||||
|
endpoint: function(endpointID) {
|
||||||
|
return Endpoints.get({id: endpointID}).$promise;
|
||||||
|
},
|
||||||
|
endpoints: function() {
|
||||||
|
return Endpoints.query({}).$promise;
|
||||||
|
},
|
||||||
|
updateEndpoint: function(ID, name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, type) {
|
||||||
|
var endpoint = {
|
||||||
|
id: ID,
|
||||||
|
Name: name,
|
||||||
|
URL: type === 'local' ? ("unix://" + URL) : ("tcp://" + URL),
|
||||||
|
TLS: TLS
|
||||||
|
};
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Endpoints.update({}, endpoint, function success(data) {
|
||||||
|
FileUploadService.uploadTLSFilesForEndpoint(ID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
||||||
|
deferred.notify({upload: false});
|
||||||
|
deferred.resolve(data);
|
||||||
|
}, function error(err) {
|
||||||
|
deferred.notify({upload: false});
|
||||||
|
deferred.reject({msg: 'Unable to upload TLS certs', err: err});
|
||||||
|
});
|
||||||
|
}, function error(err) {
|
||||||
|
deferred.reject({msg: 'Unable to update endpoint', err: err});
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
deleteEndpoint: function(endpointID) {
|
||||||
|
return Endpoints.remove({id: endpointID}).$promise;
|
||||||
|
},
|
||||||
|
createLocalEndpoint: function(name, URL, TLS, active) {
|
||||||
|
var endpoint = {
|
||||||
|
Name: "local",
|
||||||
|
URL: "unix:///var/run/docker.sock",
|
||||||
|
TLS: false
|
||||||
|
};
|
||||||
|
return Endpoints.create({active: active}, endpoint).$promise;
|
||||||
|
},
|
||||||
|
createRemoteEndpoint: function(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, active) {
|
||||||
|
var endpoint = {
|
||||||
|
Name: name,
|
||||||
|
URL: 'tcp://' + URL,
|
||||||
|
TLS: TLS
|
||||||
|
};
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Endpoints.create({active: active}, endpoint, function success(data) {
|
||||||
|
var endpointID = data.Id;
|
||||||
|
if (TLS) {
|
||||||
|
deferred.notify({upload: true});
|
||||||
|
FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
||||||
|
deferred.notify({upload: false});
|
||||||
|
if (active) {
|
||||||
|
Endpoints.setActiveEndpoint({}, {id: endpointID}, function success(data) {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}, function error(err) {
|
||||||
|
deferred.reject({msg: 'Unable to create endpoint', err: err});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
}, function error(err) {
|
||||||
|
deferred.notify({upload: false});
|
||||||
|
deferred.reject({msg: 'Unable to upload TLS certs', err: err});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
}, function error(err) {
|
||||||
|
deferred.reject({msg: 'Unable to create endpoint', err: err});
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,44 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('FileUploadService', ['$q', 'Upload', function FileUploadFactory($q, Upload) {
|
||||||
|
'use strict';
|
||||||
|
function uploadFile(url, file) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Upload.upload({
|
||||||
|
url: url,
|
||||||
|
data: { file: file }
|
||||||
|
}).then(function success(data) {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}, function error(e) {
|
||||||
|
deferred.reject(e);
|
||||||
|
}, function progress(evt) {
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uploadTLSFilesForEndpoint: function(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
var queue = [];
|
||||||
|
|
||||||
|
if (TLSCAFile !== null) {
|
||||||
|
var uploadTLSCA = uploadFile('api/upload/tls/' + endpointID + '/ca', TLSCAFile);
|
||||||
|
queue.push(uploadTLSCA);
|
||||||
|
}
|
||||||
|
if (TLSCertFile !== null) {
|
||||||
|
var uploadTLSCert = uploadFile('api/upload/tls/' + endpointID + '/cert', TLSCertFile);
|
||||||
|
queue.push(uploadTLSCert);
|
||||||
|
}
|
||||||
|
if (TLSKeyFile !== null) {
|
||||||
|
var uploadTLSKey = uploadFile('api/upload/tls/' + endpointID + '/key', TLSKeyFile);
|
||||||
|
queue.push(uploadTLSKey);
|
||||||
|
}
|
||||||
|
$q.all(queue).then(function (data) {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}, function (err) {
|
||||||
|
deferred.reject(err);
|
||||||
|
}, function update(evt) {
|
||||||
|
deferred.notify(evt);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,55 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('LineChart', ['Settings', function LineChartFactory(Settings) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
build: function (id, data, getkey) {
|
||||||
|
var chart = new Chart($(id).get(0).getContext("2d"));
|
||||||
|
var map = {};
|
||||||
|
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var c = data[i];
|
||||||
|
var key = getkey(c);
|
||||||
|
|
||||||
|
var count = map[key];
|
||||||
|
if (count === undefined) {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
map[key] = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels = [];
|
||||||
|
data = [];
|
||||||
|
var keys = Object.keys(map);
|
||||||
|
var max = 1;
|
||||||
|
|
||||||
|
for (i = keys.length - 1; i > -1; i--) {
|
||||||
|
var k = keys[i];
|
||||||
|
labels.push(k);
|
||||||
|
data.push(map[k]);
|
||||||
|
if (map[k] > max) {
|
||||||
|
max = map[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var steps = Math.min(max, 10);
|
||||||
|
var dataset = {
|
||||||
|
fillColor: "rgba(151,187,205,0.5)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
chart.Line({
|
||||||
|
labels: labels,
|
||||||
|
datasets: [dataset]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scaleStepWidth: Math.ceil(max / steps),
|
||||||
|
pointDotRadius: 1,
|
||||||
|
scaleIntegersOnly: true,
|
||||||
|
scaleOverride: true,
|
||||||
|
scaleSteps: steps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,36 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('LocalStorage', ['localStorageService', function LocalStorageFactory(localStorageService) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
storeEndpointState: function(state) {
|
||||||
|
localStorageService.set('ENDPOINT_STATE', state);
|
||||||
|
},
|
||||||
|
getEndpointState: function() {
|
||||||
|
return localStorageService.get('ENDPOINT_STATE');
|
||||||
|
},
|
||||||
|
storeApplicationState: function(state) {
|
||||||
|
localStorageService.set('APPLICATION_STATE', state);
|
||||||
|
},
|
||||||
|
getApplicationState: function() {
|
||||||
|
return localStorageService.get('APPLICATION_STATE');
|
||||||
|
},
|
||||||
|
storeJWT: function(jwt) {
|
||||||
|
localStorageService.set('JWT', jwt);
|
||||||
|
},
|
||||||
|
getJWT: function() {
|
||||||
|
return localStorageService.get('JWT');
|
||||||
|
},
|
||||||
|
deleteJWT: function() {
|
||||||
|
localStorageService.remove('JWT');
|
||||||
|
},
|
||||||
|
storePaginationCount: function(key, count) {
|
||||||
|
localStorageService.cookie.set('pagination_' + key, count);
|
||||||
|
},
|
||||||
|
getPaginationCount: function(key) {
|
||||||
|
return localStorageService.cookie.get('pagination_' + key);
|
||||||
|
},
|
||||||
|
clean: function() {
|
||||||
|
localStorageService.clearAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,38 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('Messages', ['$sanitize', function MessagesFactory($sanitize) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
send: function (title, text) {
|
||||||
|
$.gritter.add({
|
||||||
|
title: $sanitize(title),
|
||||||
|
text: $sanitize(text),
|
||||||
|
time: 2000,
|
||||||
|
before_open: function () {
|
||||||
|
if ($('.gritter-item-wrapper').length === 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (title, e, fallbackText) {
|
||||||
|
var msg = fallbackText;
|
||||||
|
if (e.data && e.data.message) {
|
||||||
|
msg = e.data.message;
|
||||||
|
} else if (e.message) {
|
||||||
|
msg = e.message;
|
||||||
|
} else if (e.data && e.data.length > 0 && e.data[0].message) {
|
||||||
|
msg = e.data[0].message;
|
||||||
|
}
|
||||||
|
$.gritter.add({
|
||||||
|
title: $sanitize(title),
|
||||||
|
text: $sanitize(msg),
|
||||||
|
time: 10000,
|
||||||
|
before_open: function () {
|
||||||
|
if ($('.gritter-item-wrapper').length === 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,17 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('Pagination', ['LocalStorage', 'Settings', function PaginationFactory(LocalStorage, Settings) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
getPaginationCount: function(key) {
|
||||||
|
var storedCount = LocalStorage.getPaginationCount(key);
|
||||||
|
var paginationCount = Settings.pagination_count;
|
||||||
|
if (storedCount !== null) {
|
||||||
|
paginationCount = storedCount;
|
||||||
|
}
|
||||||
|
return '' + paginationCount;
|
||||||
|
},
|
||||||
|
setPaginationCount: function(key, count) {
|
||||||
|
LocalStorage.storePaginationCount(key, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,17 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', 'PAGINATION_MAX_ITEMS', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION, PAGINATION_MAX_ITEMS) {
|
||||||
|
'use strict';
|
||||||
|
var url = DOCKER_ENDPOINT;
|
||||||
|
if (DOCKER_PORT) {
|
||||||
|
url = url + DOCKER_PORT + '\\' + DOCKER_PORT;
|
||||||
|
}
|
||||||
|
var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true';
|
||||||
|
return {
|
||||||
|
displayAll: true,
|
||||||
|
endpoint: DOCKER_ENDPOINT,
|
||||||
|
uiVersion: UI_VERSION,
|
||||||
|
url: url,
|
||||||
|
firstLoad: firstLoad,
|
||||||
|
pagination_count: PAGINATION_MAX_ITEMS
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -0,0 +1,66 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('StateManager', ['$q', 'Config', 'Info', 'InfoHelper', 'Version', 'LocalStorage', function StateManagerFactory($q, Config, Info, InfoHelper, Version, LocalStorage) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
loading: true,
|
||||||
|
application: {},
|
||||||
|
endpoint: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialize: function() {
|
||||||
|
var endpointState = LocalStorage.getEndpointState();
|
||||||
|
if (endpointState) {
|
||||||
|
state.endpoint = endpointState;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deferred = $q.defer();
|
||||||
|
var applicationState = LocalStorage.getApplicationState();
|
||||||
|
if (applicationState) {
|
||||||
|
state.application = applicationState;
|
||||||
|
state.loading = false;
|
||||||
|
deferred.resolve(state);
|
||||||
|
} else {
|
||||||
|
Config.$promise.then(function success(data) {
|
||||||
|
state.application.authentication = data.authentication;
|
||||||
|
state.application.endpointManagement = data.endpointManagement;
|
||||||
|
state.application.logo = data.logo;
|
||||||
|
LocalStorage.storeApplicationState(state.application);
|
||||||
|
state.loading = false;
|
||||||
|
deferred.resolve(state);
|
||||||
|
}, function error(err) {
|
||||||
|
state.loading = false;
|
||||||
|
deferred.reject({msg: 'Unable to retrieve server configuration', err: err});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
clean: function() {
|
||||||
|
state.endpoint = {};
|
||||||
|
},
|
||||||
|
updateEndpointState: function(loading) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
if (loading) {
|
||||||
|
state.loading = true;
|
||||||
|
}
|
||||||
|
$q.all([Info.get({}).$promise, Version.get({}).$promise])
|
||||||
|
.then(function success(data) {
|
||||||
|
var endpointMode = InfoHelper.determineEndpointMode(data[0]);
|
||||||
|
var endpointAPIVersion = parseFloat(data[1].ApiVersion);
|
||||||
|
state.endpoint.mode = endpointMode;
|
||||||
|
state.endpoint.apiVersion = endpointAPIVersion;
|
||||||
|
LocalStorage.storeEndpointState(state.endpoint);
|
||||||
|
state.loading = false;
|
||||||
|
deferred.resolve();
|
||||||
|
}, function error(err) {
|
||||||
|
state.loading = false;
|
||||||
|
deferred.reject({msg: 'Unable to connect to the Docker endpoint', err: err});
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
getState: function() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -1,170 +0,0 @@
|
||||||
angular.module('portainer.helpers', [])
|
|
||||||
.factory('InfoHelper', [function InfoHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
determineEndpointMode: function(info) {
|
|
||||||
var mode = {
|
|
||||||
provider: '',
|
|
||||||
role: ''
|
|
||||||
};
|
|
||||||
if (_.startsWith(info.ServerVersion, 'swarm')) {
|
|
||||||
mode.provider = "DOCKER_SWARM";
|
|
||||||
if (info.SystemStatus[0][1] === 'primary') {
|
|
||||||
mode.role = "PRIMARY";
|
|
||||||
} else {
|
|
||||||
mode.role = "REPLICA";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
|
|
||||||
mode.provider = "DOCKER_STANDALONE";
|
|
||||||
} else {
|
|
||||||
mode.provider = "DOCKER_SWARM_MODE";
|
|
||||||
if (info.Swarm.ControlAvailable) {
|
|
||||||
mode.role = "MANAGER";
|
|
||||||
} else {
|
|
||||||
mode.role = "WORKER";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('LabelHelper', [function LabelHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
fromLabelHashToKeyValue: function(labels) {
|
|
||||||
if (labels) {
|
|
||||||
return Object.keys(labels).map(function(key) {
|
|
||||||
return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
fromKeyValueToLabelHash: function(labelKV) {
|
|
||||||
var labels = {};
|
|
||||||
if (labelKV) {
|
|
||||||
labelKV.forEach(function(label) {
|
|
||||||
labels[label.key] = label.value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('ImageHelper', [function ImageHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
createImageConfigForCommit: function(imageName, registry) {
|
|
||||||
var imageNameAndTag = imageName.split(':');
|
|
||||||
var image = imageNameAndTag[0];
|
|
||||||
if (registry) {
|
|
||||||
image = registry + '/' + imageNameAndTag[0];
|
|
||||||
}
|
|
||||||
var imageConfig = {
|
|
||||||
repo: image,
|
|
||||||
tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
|
|
||||||
};
|
|
||||||
return imageConfig;
|
|
||||||
},
|
|
||||||
createImageConfigForContainer: function (imageName, registry) {
|
|
||||||
var imageNameAndTag = imageName.split(':');
|
|
||||||
var image = imageNameAndTag[0];
|
|
||||||
if (registry) {
|
|
||||||
image = registry + '/' + imageNameAndTag[0];
|
|
||||||
}
|
|
||||||
var imageConfig = {
|
|
||||||
fromImage: image,
|
|
||||||
tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
|
|
||||||
};
|
|
||||||
return imageConfig;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('ContainerHelper', [function ContainerHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
hideContainers: function(containers, containersToHideLabels) {
|
|
||||||
return containers.filter(function (container) {
|
|
||||||
var filterContainer = false;
|
|
||||||
containersToHideLabels.forEach(function(label, index) {
|
|
||||||
if (_.has(container.Labels, label.name) &&
|
|
||||||
container.Labels[label.name] === label.value) {
|
|
||||||
filterContainer = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!filterContainer) {
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('ServiceHelper', [function ServiceHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
serviceToConfig: function(service) {
|
|
||||||
return {
|
|
||||||
Name: service.Spec.Name,
|
|
||||||
Labels: service.Spec.Labels,
|
|
||||||
TaskTemplate: service.Spec.TaskTemplate,
|
|
||||||
Mode: service.Spec.Mode,
|
|
||||||
UpdateConfig: service.Spec.UpdateConfig,
|
|
||||||
Networks: service.Spec.Networks,
|
|
||||||
EndpointSpec: service.Spec.EndpointSpec
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('NodeHelper', [function NodeHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
nodeToConfig: function(node) {
|
|
||||||
return {
|
|
||||||
Name: node.Spec.Name,
|
|
||||||
Role: node.Spec.Role,
|
|
||||||
Labels: node.Spec.Labels,
|
|
||||||
Availability: node.Spec.Availability
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
getPortBindings: function(ports) {
|
|
||||||
var bindings = [];
|
|
||||||
ports.forEach(function (port) {
|
|
||||||
var portAndProtocol = _.split(port, '/');
|
|
||||||
var binding = {
|
|
||||||
containerPort: portAndProtocol[0],
|
|
||||||
protocol: portAndProtocol[1]
|
|
||||||
};
|
|
||||||
bindings.push(binding);
|
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
},
|
|
||||||
//Not used atm, may prove useful later
|
|
||||||
getVolumeBindings: function(volumes) {
|
|
||||||
var bindings = [];
|
|
||||||
volumes.forEach(function (volume) {
|
|
||||||
bindings.push({ containerPath: volume });
|
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
},
|
|
||||||
//Not used atm, may prove useful later
|
|
||||||
getEnvBindings: function(env) {
|
|
||||||
var bindings = [];
|
|
||||||
env.forEach(function (envvar) {
|
|
||||||
var binding = {
|
|
||||||
name: envvar.name
|
|
||||||
};
|
|
||||||
if (envvar.set) {
|
|
||||||
binding.value = envvar.set;
|
|
||||||
}
|
|
||||||
bindings.push(binding);
|
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
|
@ -1,594 +0,0 @@
|
||||||
angular.module('portainer.services', ['ngResource', 'ngSanitize'])
|
|
||||||
.factory('Container', ['$resource', 'Settings', function ContainerFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// Resource for interacting with the docker containers
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-1-containers
|
|
||||||
return $resource(Settings.url + '/containers/:id/:action', {
|
|
||||||
name: '@name'
|
|
||||||
}, {
|
|
||||||
query: {method: 'GET', params: {all: 0, action: 'json', filters: '@filters' }, isArray: true},
|
|
||||||
get: {method: 'GET', params: {action: 'json'}},
|
|
||||||
stop: {method: 'POST', params: {id: '@id', t: 5, action: 'stop'}},
|
|
||||||
restart: {method: 'POST', params: {id: '@id', t: 5, action: 'restart'}},
|
|
||||||
kill: {method: 'POST', params: {id: '@id', action: 'kill'}},
|
|
||||||
pause: {method: 'POST', params: {id: '@id', action: 'pause'}},
|
|
||||||
unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}},
|
|
||||||
changes: {method: 'GET', params: {action: 'changes'}, isArray: true},
|
|
||||||
stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000},
|
|
||||||
start: {
|
|
||||||
method: 'POST', params: {id: '@id', action: 'start'},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
method: 'POST', params: {action: 'create'},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
},
|
|
||||||
remove: {
|
|
||||||
method: 'DELETE', params: {id: '@id', v: 0},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
},
|
|
||||||
rename: {
|
|
||||||
method: 'POST', params: {id: '@id', action: 'rename', name: '@name'},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
},
|
|
||||||
exec: {
|
|
||||||
method: 'POST', params: {id: '@id', action: 'exec'},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Service', ['$resource', 'Settings', function ServiceFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-9-services
|
|
||||||
return $resource(Settings.url + '/services/:id/:action', {}, {
|
|
||||||
get: { method: 'GET', params: {id: '@id'} },
|
|
||||||
query: { method: 'GET', isArray: true },
|
|
||||||
create: { method: 'POST', params: {action: 'create'} },
|
|
||||||
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
|
||||||
remove: { method: 'DELETE', params: {id: '@id'} }
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Task', ['$resource', 'Settings', function TaskFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-9-services
|
|
||||||
return $resource(Settings.url + '/tasks/:id', {}, {
|
|
||||||
get: { method: 'GET', params: {id: '@id'} },
|
|
||||||
query: { method: 'GET', isArray: true, params: {filters: '@filters'} }
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Exec', ['$resource', 'Settings', function ExecFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/exec-resize
|
|
||||||
return $resource(Settings.url + '/exec/:id/:action', {}, {
|
|
||||||
resize: {
|
|
||||||
method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'},
|
|
||||||
transformResponse: genericHandler
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('ContainerCommit', ['$resource', '$http', 'Settings', function ContainerCommitFactory($resource, $http, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#create-a-new-image-from-a-container-s-changes
|
|
||||||
return $resource(Settings.url + '/commit', {}, {
|
|
||||||
commit: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('ContainerLogs', ['$resource', '$http', 'Settings', function ContainerLogsFactory($resource, $http, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#get-container-logs
|
|
||||||
return {
|
|
||||||
get: function (id, params, callback) {
|
|
||||||
$http({
|
|
||||||
method: 'GET',
|
|
||||||
url: Settings.url + '/containers/' + id + '/logs',
|
|
||||||
params: {
|
|
||||||
'stdout': params.stdout || 0,
|
|
||||||
'stderr': params.stderr || 0,
|
|
||||||
'timestamps': params.timestamps || 0,
|
|
||||||
'tail': params.tail || 'all'
|
|
||||||
}
|
|
||||||
}).success(callback).error(function (data, status, headers, config) {
|
|
||||||
console.log(error, data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('ContainerTop', ['$http', 'Settings', function ($http, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#list-processes-running-inside-a-container
|
|
||||||
return {
|
|
||||||
get: function (id, params, callback, errorCallback) {
|
|
||||||
$http({
|
|
||||||
method: 'GET',
|
|
||||||
url: Settings.url + '/containers/' + id + '/top',
|
|
||||||
params: {
|
|
||||||
ps_args: params.ps_args
|
|
||||||
}
|
|
||||||
}).success(callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('Image', ['$resource', 'Settings', function ImageFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-2-images
|
|
||||||
return $resource(Settings.url + '/images/:id/:action', {}, {
|
|
||||||
query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true},
|
|
||||||
get: {method: 'GET', params: {action: 'json'}},
|
|
||||||
search: {method: 'GET', params: {action: 'search'}},
|
|
||||||
history: {method: 'GET', params: {action: 'history'}, isArray: true},
|
|
||||||
insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
|
|
||||||
tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}},
|
|
||||||
inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
|
|
||||||
push: {
|
|
||||||
method: 'POST', params: {action: 'push', id: '@tag'},
|
|
||||||
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
|
|
||||||
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
|
||||||
},
|
|
||||||
remove: {
|
|
||||||
method: 'DELETE', params: {id: '@id'},
|
|
||||||
isArray: true, transformResponse: deleteImageHandler
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/monitor-docker-s-events
|
|
||||||
return $resource(Settings.url + '/events', {}, {
|
|
||||||
query: {
|
|
||||||
method: 'GET', params: {since: '@since', until: '@until'},
|
|
||||||
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#show-the-docker-version-information
|
|
||||||
return $resource(Settings.url + '/version', {}, {
|
|
||||||
get: {method: 'GET'}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-7-nodes
|
|
||||||
return $resource(Settings.url + '/nodes/:id/:action', {}, {
|
|
||||||
query: {method: 'GET', isArray: true},
|
|
||||||
get: {method: 'GET', params: {id: '@id'}},
|
|
||||||
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
|
||||||
remove: { method: 'DELETE', params: {id: '@id'} }
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-8-swarm
|
|
||||||
return $resource(Settings.url + '/swarm', {}, {
|
|
||||||
get: {method: 'GET'}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#display-system-wide-information
|
|
||||||
return $resource(Settings.url + '/info', {}, {
|
|
||||||
get: {method: 'GET'}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
|
|
||||||
return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
|
|
||||||
query: {method: 'GET', isArray: true},
|
|
||||||
get: {method: 'GET'},
|
|
||||||
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
|
|
||||||
remove: { method: 'DELETE', transformResponse: genericHandler },
|
|
||||||
connect: {method: 'POST', params: {action: 'connect'}},
|
|
||||||
disconnect: {method: 'POST', params: {action: 'disconnect'}}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Volume', ['$resource', 'Settings', function VolumeFactory($resource, Settings) {
|
|
||||||
'use strict';
|
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
|
|
||||||
return $resource(Settings.url + '/volumes/:name/:action', {name: '@name'}, {
|
|
||||||
query: {method: 'GET'},
|
|
||||||
get: {method: 'GET'},
|
|
||||||
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
|
|
||||||
remove: {
|
|
||||||
method: 'DELETE', transformResponse: genericHandler
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Config', ['$resource', 'CONFIG_ENDPOINT', function ConfigFactory($resource, CONFIG_ENDPOINT) {
|
|
||||||
return $resource(CONFIG_ENDPOINT).get();
|
|
||||||
}])
|
|
||||||
.factory('Templates', ['$resource', 'TEMPLATES_ENDPOINT', function TemplatesFactory($resource, TEMPLATES_ENDPOINT) {
|
|
||||||
return $resource(TEMPLATES_ENDPOINT, {}, {
|
|
||||||
get: {method: 'GET', isArray: true}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', 'PAGINATION_MAX_ITEMS', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION, PAGINATION_MAX_ITEMS) {
|
|
||||||
'use strict';
|
|
||||||
var url = DOCKER_ENDPOINT;
|
|
||||||
if (DOCKER_PORT) {
|
|
||||||
url = url + DOCKER_PORT + '\\' + DOCKER_PORT;
|
|
||||||
}
|
|
||||||
var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true';
|
|
||||||
return {
|
|
||||||
displayAll: true,
|
|
||||||
endpoint: DOCKER_ENDPOINT,
|
|
||||||
uiVersion: UI_VERSION,
|
|
||||||
url: url,
|
|
||||||
firstLoad: firstLoad,
|
|
||||||
pagination_count: PAGINATION_MAX_ITEMS
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('Auth', ['$resource', 'AUTH_ENDPOINT', function AuthFactory($resource, AUTH_ENDPOINT) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(AUTH_ENDPOINT, {}, {
|
|
||||||
login: {
|
|
||||||
method: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Users', ['$resource', 'USERS_ENDPOINT', function UsersFactory($resource, USERS_ENDPOINT) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(USERS_ENDPOINT + '/:username/:action', {}, {
|
|
||||||
create: { method: 'POST' },
|
|
||||||
get: { method: 'GET', params: { username: '@username' } },
|
|
||||||
update: { method: 'PUT', params: { username: '@username' } },
|
|
||||||
checkPassword: { method: 'POST', params: { username: '@username', action: 'passwd' } },
|
|
||||||
checkAdminUser: { method: 'GET', params: { username: 'admin', action: 'check' } },
|
|
||||||
initAdminUser: { method: 'POST', params: { username: 'admin', action: 'init' } }
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Authentication', ['$q', '$rootScope', 'Auth', 'jwtHelper', 'LocalStorage', 'StateManager', function AuthenticationFactory($q, $rootScope, Auth, jwtHelper, LocalStorage, StateManager) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
init: function() {
|
|
||||||
var jwt = LocalStorage.getJWT();
|
|
||||||
if (jwt) {
|
|
||||||
var tokenPayload = jwtHelper.decodeToken(jwt);
|
|
||||||
$rootScope.username = tokenPayload.username;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
login: function(username, password) {
|
|
||||||
return $q(function (resolve, reject) {
|
|
||||||
Auth.login({username: username, password: password}).$promise
|
|
||||||
.then(function(data) {
|
|
||||||
LocalStorage.storeJWT(data.jwt);
|
|
||||||
$rootScope.username = username;
|
|
||||||
resolve();
|
|
||||||
}, function() {
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
logout: function() {
|
|
||||||
StateManager.clean();
|
|
||||||
LocalStorage.clean();
|
|
||||||
},
|
|
||||||
isAuthenticated: function() {
|
|
||||||
var jwt = LocalStorage.getJWT();
|
|
||||||
return jwt && !jwtHelper.isTokenExpired(jwt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('FileUploadService', ['$q', 'Upload', function FileUploadFactory($q, Upload) {
|
|
||||||
'use strict';
|
|
||||||
function uploadFile(url, file) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
Upload.upload({
|
|
||||||
url: url,
|
|
||||||
data: { file: file }
|
|
||||||
}).then(function success(data) {
|
|
||||||
deferred.resolve(data);
|
|
||||||
}, function error(e) {
|
|
||||||
deferred.reject(e);
|
|
||||||
}, function progress(evt) {
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
uploadTLSFilesForEndpoint: function(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
var queue = [];
|
|
||||||
|
|
||||||
if (TLSCAFile !== null) {
|
|
||||||
var uploadTLSCA = uploadFile('api/upload/tls/' + endpointID + '/ca', TLSCAFile);
|
|
||||||
queue.push(uploadTLSCA);
|
|
||||||
}
|
|
||||||
if (TLSCertFile !== null) {
|
|
||||||
var uploadTLSCert = uploadFile('api/upload/tls/' + endpointID + '/cert', TLSCertFile);
|
|
||||||
queue.push(uploadTLSCert);
|
|
||||||
}
|
|
||||||
if (TLSKeyFile !== null) {
|
|
||||||
var uploadTLSKey = uploadFile('api/upload/tls/' + endpointID + '/key', TLSKeyFile);
|
|
||||||
queue.push(uploadTLSKey);
|
|
||||||
}
|
|
||||||
$q.all(queue).then(function (data) {
|
|
||||||
deferred.resolve(data);
|
|
||||||
}, function (err) {
|
|
||||||
deferred.reject(err);
|
|
||||||
}, function update(evt) {
|
|
||||||
deferred.notify(evt);
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('Endpoints', ['$resource', 'ENDPOINTS_ENDPOINT', function EndpointsFactory($resource, ENDPOINTS_ENDPOINT) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(ENDPOINTS_ENDPOINT + '/:id/:action', {}, {
|
|
||||||
create: { method: 'POST' },
|
|
||||||
query: { method: 'GET', isArray: true },
|
|
||||||
get: { method: 'GET', params: { id: '@id' } },
|
|
||||||
update: { method: 'PUT', params: { id: '@id' } },
|
|
||||||
remove: { method: 'DELETE', params: { id: '@id'} },
|
|
||||||
getActiveEndpoint: { method: 'GET', params: { id: '0' } },
|
|
||||||
setActiveEndpoint: { method: 'POST', params: { id: '@id', action: 'active' } }
|
|
||||||
});
|
|
||||||
}])
|
|
||||||
.factory('Pagination', ['LocalStorage', 'Settings', function PaginationFactory(LocalStorage, Settings) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
getPaginationCount: function(key) {
|
|
||||||
var storedCount = LocalStorage.getPaginationCount(key);
|
|
||||||
var paginationCount = Settings.pagination_count;
|
|
||||||
if (storedCount !== null) {
|
|
||||||
paginationCount = storedCount;
|
|
||||||
}
|
|
||||||
return '' + paginationCount;
|
|
||||||
},
|
|
||||||
setPaginationCount: function(key, count) {
|
|
||||||
LocalStorage.storePaginationCount(key, count);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('LocalStorage', ['localStorageService', function LocalStorageFactory(localStorageService) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
storeEndpointState: function(state) {
|
|
||||||
localStorageService.set('ENDPOINT_STATE', state);
|
|
||||||
},
|
|
||||||
getEndpointState: function() {
|
|
||||||
return localStorageService.get('ENDPOINT_STATE');
|
|
||||||
},
|
|
||||||
storeJWT: function(jwt) {
|
|
||||||
localStorageService.set('JWT', jwt);
|
|
||||||
},
|
|
||||||
getJWT: function() {
|
|
||||||
return localStorageService.get('JWT');
|
|
||||||
},
|
|
||||||
deleteJWT: function() {
|
|
||||||
localStorageService.remove('JWT');
|
|
||||||
},
|
|
||||||
storePaginationCount: function(key, count) {
|
|
||||||
localStorageService.cookie.set('pagination_' + key, count);
|
|
||||||
},
|
|
||||||
getPaginationCount: function(key) {
|
|
||||||
return localStorageService.cookie.get('pagination_' + key);
|
|
||||||
},
|
|
||||||
clean: function() {
|
|
||||||
localStorageService.clearAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('StateManager', ['$q', 'Info', 'InfoHelper', 'Version', 'LocalStorage', function StateManagerFactory($q, Info, InfoHelper, Version, LocalStorage) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var state = {
|
|
||||||
loading: true,
|
|
||||||
application: {},
|
|
||||||
endpoint: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: function() {
|
|
||||||
var endpointState = LocalStorage.getEndpointState();
|
|
||||||
if (endpointState) {
|
|
||||||
state.endpoint = endpointState;
|
|
||||||
}
|
|
||||||
state.loading = false;
|
|
||||||
},
|
|
||||||
clean: function() {
|
|
||||||
state.endpoint = {};
|
|
||||||
},
|
|
||||||
updateEndpointState: function(loading) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
if (loading) {
|
|
||||||
state.loading = true;
|
|
||||||
}
|
|
||||||
$q.all([Info.get({}).$promise, Version.get({}).$promise])
|
|
||||||
.then(function success(data) {
|
|
||||||
var endpointMode = InfoHelper.determineEndpointMode(data[0]);
|
|
||||||
var endpointAPIVersion = parseFloat(data[1].ApiVersion);
|
|
||||||
state.endpoint.mode = endpointMode;
|
|
||||||
state.endpoint.apiVersion = endpointAPIVersion;
|
|
||||||
LocalStorage.storeEndpointState(state.endpoint);
|
|
||||||
state.loading = false;
|
|
||||||
deferred.resolve();
|
|
||||||
}, function error(err) {
|
|
||||||
state.loading = false;
|
|
||||||
deferred.reject({msg: 'Unable to connect to the Docker endpoint', err: err});
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
getState: function() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
getActive: function() {
|
|
||||||
return Endpoints.getActiveEndpoint().$promise;
|
|
||||||
},
|
|
||||||
setActive: function(endpointID) {
|
|
||||||
return Endpoints.setActiveEndpoint({id: endpointID}).$promise;
|
|
||||||
},
|
|
||||||
endpoint: function(endpointID) {
|
|
||||||
return Endpoints.get({id: endpointID}).$promise;
|
|
||||||
},
|
|
||||||
endpoints: function() {
|
|
||||||
return Endpoints.query({}).$promise;
|
|
||||||
},
|
|
||||||
updateEndpoint: function(ID, name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, type) {
|
|
||||||
var endpoint = {
|
|
||||||
id: ID,
|
|
||||||
Name: name,
|
|
||||||
URL: type === 'local' ? ("unix://" + URL) : ("tcp://" + URL),
|
|
||||||
TLS: TLS
|
|
||||||
};
|
|
||||||
var deferred = $q.defer();
|
|
||||||
Endpoints.update({}, endpoint, function success(data) {
|
|
||||||
FileUploadService.uploadTLSFilesForEndpoint(ID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
|
||||||
deferred.notify({upload: false});
|
|
||||||
deferred.resolve(data);
|
|
||||||
}, function error(err) {
|
|
||||||
deferred.notify({upload: false});
|
|
||||||
deferred.reject({msg: 'Unable to upload TLS certs', err: err});
|
|
||||||
});
|
|
||||||
}, function error(err) {
|
|
||||||
deferred.reject({msg: 'Unable to update endpoint', err: err});
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
deleteEndpoint: function(endpointID) {
|
|
||||||
return Endpoints.remove({id: endpointID}).$promise;
|
|
||||||
},
|
|
||||||
createLocalEndpoint: function(name, URL, TLS, active) {
|
|
||||||
var endpoint = {
|
|
||||||
Name: "local",
|
|
||||||
URL: "unix:///var/run/docker.sock",
|
|
||||||
TLS: false
|
|
||||||
};
|
|
||||||
return Endpoints.create({active: active}, endpoint).$promise;
|
|
||||||
},
|
|
||||||
createRemoteEndpoint: function(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, active) {
|
|
||||||
var endpoint = {
|
|
||||||
Name: name,
|
|
||||||
URL: 'tcp://' + URL,
|
|
||||||
TLS: TLS
|
|
||||||
};
|
|
||||||
var deferred = $q.defer();
|
|
||||||
Endpoints.create({active: active}, endpoint, function success(data) {
|
|
||||||
var endpointID = data.Id;
|
|
||||||
if (TLS) {
|
|
||||||
deferred.notify({upload: true});
|
|
||||||
FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
|
||||||
deferred.notify({upload: false});
|
|
||||||
if (active) {
|
|
||||||
Endpoints.setActiveEndpoint({}, {id: endpointID}, function success(data) {
|
|
||||||
deferred.resolve(data);
|
|
||||||
}, function error(err) {
|
|
||||||
deferred.reject({msg: 'Unable to create endpoint', err: err});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deferred.resolve(data);
|
|
||||||
}
|
|
||||||
}, function error(err) {
|
|
||||||
deferred.notify({upload: false});
|
|
||||||
deferred.reject({msg: 'Unable to upload TLS certs', err: err});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deferred.resolve(data);
|
|
||||||
}
|
|
||||||
}, function error(err) {
|
|
||||||
deferred.reject({msg: 'Unable to create endpoint', err: err});
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('Messages', ['$rootScope', '$sanitize', function MessagesFactory($rootScope, $sanitize) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
send: function (title, text) {
|
|
||||||
$.gritter.add({
|
|
||||||
title: $sanitize(title),
|
|
||||||
text: $sanitize(text),
|
|
||||||
time: 2000,
|
|
||||||
before_open: function () {
|
|
||||||
if ($('.gritter-item-wrapper').length === 3) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: function (title, e, fallbackText) {
|
|
||||||
var msg = fallbackText;
|
|
||||||
if (e.data && e.data.message) {
|
|
||||||
msg = e.data.message;
|
|
||||||
} else if (e.message) {
|
|
||||||
msg = e.message;
|
|
||||||
} else if (e.data && e.data.length > 0 && e.data[0].message) {
|
|
||||||
msg = e.data[0].message;
|
|
||||||
}
|
|
||||||
$.gritter.add({
|
|
||||||
title: $sanitize(title),
|
|
||||||
text: $sanitize(msg),
|
|
||||||
time: 10000,
|
|
||||||
before_open: function () {
|
|
||||||
if ($('.gritter-item-wrapper').length === 4) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
.factory('LineChart', ['Settings', function LineChartFactory(Settings) {
|
|
||||||
'use strict';
|
|
||||||
return {
|
|
||||||
build: function (id, data, getkey) {
|
|
||||||
var chart = new Chart($(id).get(0).getContext("2d"));
|
|
||||||
var map = {};
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
var c = data[i];
|
|
||||||
var key = getkey(c);
|
|
||||||
|
|
||||||
var count = map[key];
|
|
||||||
if (count === undefined) {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
map[key] = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
var labels = [];
|
|
||||||
data = [];
|
|
||||||
var keys = Object.keys(map);
|
|
||||||
var max = 1;
|
|
||||||
|
|
||||||
for (i = keys.length - 1; i > -1; i--) {
|
|
||||||
var k = keys[i];
|
|
||||||
labels.push(k);
|
|
||||||
data.push(map[k]);
|
|
||||||
if (map[k] > max) {
|
|
||||||
max = map[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var steps = Math.min(max, 10);
|
|
||||||
var dataset = {
|
|
||||||
fillColor: "rgba(151,187,205,0.5)",
|
|
||||||
strokeColor: "rgba(151,187,205,1)",
|
|
||||||
pointColor: "rgba(151,187,205,1)",
|
|
||||||
pointStrokeColor: "#fff",
|
|
||||||
data: data
|
|
||||||
};
|
|
||||||
chart.Line({
|
|
||||||
labels: labels,
|
|
||||||
datasets: [dataset]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scaleStepWidth: Math.ceil(max / steps),
|
|
||||||
pointDotRadius: 1,
|
|
||||||
scaleIntegersOnly: true,
|
|
||||||
scaleOverride: true,
|
|
||||||
scaleSteps: steps
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
|
@ -1,240 +0,0 @@
|
||||||
function ImageViewModel(data) {
|
|
||||||
this.Id = data.Id;
|
|
||||||
this.Tag = data.Tag;
|
|
||||||
this.Repository = data.Repository;
|
|
||||||
this.Created = data.Created;
|
|
||||||
this.Checked = false;
|
|
||||||
this.RepoTags = data.RepoTags;
|
|
||||||
this.VirtualSize = data.VirtualSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TaskViewModel(data, node_data) {
|
|
||||||
this.Id = data.ID;
|
|
||||||
this.Created = data.CreatedAt;
|
|
||||||
this.Updated = data.UpdatedAt;
|
|
||||||
this.Slot = data.Slot;
|
|
||||||
this.Status = data.Status.State;
|
|
||||||
this.Image = data.Spec.ContainerSpec ? data.Spec.ContainerSpec.Image : '';
|
|
||||||
if (node_data) {
|
|
||||||
for (var i = 0; i < node_data.length; ++i) {
|
|
||||||
if (data.NodeID === node_data[i].ID) {
|
|
||||||
this.Node = node_data[i].Description.Hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ServiceViewModel(data) {
|
|
||||||
this.Model = data;
|
|
||||||
this.Id = data.ID;
|
|
||||||
this.Name = data.Spec.Name;
|
|
||||||
this.Image = data.Spec.TaskTemplate.ContainerSpec.Image;
|
|
||||||
this.Version = data.Version.Index;
|
|
||||||
if (data.Spec.Mode.Replicated) {
|
|
||||||
this.Mode = 'replicated' ;
|
|
||||||
this.Replicas = data.Spec.Mode.Replicated.Replicas;
|
|
||||||
} else {
|
|
||||||
this.Mode = 'global';
|
|
||||||
}
|
|
||||||
this.Labels = data.Spec.Labels;
|
|
||||||
if (data.Spec.TaskTemplate.ContainerSpec) {
|
|
||||||
this.ContainerLabels = data.Spec.TaskTemplate.ContainerSpec.Labels;
|
|
||||||
}
|
|
||||||
if (data.Spec.TaskTemplate.ContainerSpec.Env) {
|
|
||||||
this.Env = data.Spec.TaskTemplate.ContainerSpec.Env;
|
|
||||||
}
|
|
||||||
if (data.Endpoint.Ports) {
|
|
||||||
this.Ports = data.Endpoint.Ports;
|
|
||||||
}
|
|
||||||
if (data.Spec.UpdateConfig) {
|
|
||||||
this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1;
|
|
||||||
this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0;
|
|
||||||
this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause';
|
|
||||||
} else {
|
|
||||||
this.UpdateParallelism = 1;
|
|
||||||
this.UpdateDelay = 0;
|
|
||||||
this.UpdateFailureAction = 'pause';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Checked = false;
|
|
||||||
this.Scale = false;
|
|
||||||
this.EditName = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NodeViewModel(data) {
|
|
||||||
this.Model = data;
|
|
||||||
this.Id = data.ID;
|
|
||||||
this.Version = data.Version.Index;
|
|
||||||
this.Name = data.Spec.Name;
|
|
||||||
this.Role = data.Spec.Role;
|
|
||||||
this.CreatedAt = data.CreatedAt;
|
|
||||||
this.UpdatedAt = data.UpdatedAt;
|
|
||||||
this.Availability = data.Spec.Availability;
|
|
||||||
|
|
||||||
var labels = data.Spec.Labels;
|
|
||||||
if (labels) {
|
|
||||||
this.Labels = Object.keys(labels).map(function(key) {
|
|
||||||
return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.Labels = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Hostname = data.Description.Hostname;
|
|
||||||
this.PlatformArchitecture = data.Description.Platform.Architecture;
|
|
||||||
this.PlatformOS = data.Description.Platform.OS;
|
|
||||||
this.CPUs = data.Description.Resources.NanoCPUs;
|
|
||||||
this.Memory = data.Description.Resources.MemoryBytes;
|
|
||||||
this.EngineVersion = data.Description.Engine.EngineVersion;
|
|
||||||
this.EngineLabels = data.Description.Engine.Labels;
|
|
||||||
this.Plugins = data.Description.Engine.Plugins;
|
|
||||||
this.Status = data.Status.State;
|
|
||||||
|
|
||||||
if (data.ManagerStatus) {
|
|
||||||
this.Leader = data.ManagerStatus.Leader;
|
|
||||||
this.Reachability = data.ManagerStatus.Reachability;
|
|
||||||
this.ManagerAddr = data.ManagerStatus.Addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContainerViewModel(data) {
|
|
||||||
this.Id = data.Id;
|
|
||||||
this.Status = data.Status;
|
|
||||||
this.State = data.State;
|
|
||||||
this.Names = data.Names;
|
|
||||||
// Unavailable in Docker < 1.10
|
|
||||||
if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
|
|
||||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
|
||||||
}
|
|
||||||
this.Image = data.Image;
|
|
||||||
this.Command = data.Command;
|
|
||||||
this.Checked = false;
|
|
||||||
this.Ports = [];
|
|
||||||
for (var i = 0; i < data.Ports.length; ++i) {
|
|
||||||
var p = data.Ports[i];
|
|
||||||
if (p.PublicPort) {
|
|
||||||
this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEventDetails(event) {
|
|
||||||
var eventAttr = event.Actor.Attributes;
|
|
||||||
var details = '';
|
|
||||||
switch (event.Type) {
|
|
||||||
case 'container':
|
|
||||||
switch (event.Action) {
|
|
||||||
case 'stop':
|
|
||||||
details = 'Container ' + eventAttr.name + ' stopped';
|
|
||||||
break;
|
|
||||||
case 'destroy':
|
|
||||||
details = 'Container ' + eventAttr.name + ' deleted';
|
|
||||||
break;
|
|
||||||
case 'create':
|
|
||||||
details = 'Container ' + eventAttr.name + ' created';
|
|
||||||
break;
|
|
||||||
case 'start':
|
|
||||||
details = 'Container ' + eventAttr.name + ' started';
|
|
||||||
break;
|
|
||||||
case 'kill':
|
|
||||||
details = 'Container ' + eventAttr.name + ' killed';
|
|
||||||
break;
|
|
||||||
case 'die':
|
|
||||||
details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
|
|
||||||
break;
|
|
||||||
case 'commit':
|
|
||||||
details = 'Container ' + eventAttr.name + ' committed';
|
|
||||||
break;
|
|
||||||
case 'restart':
|
|
||||||
details = 'Container ' + eventAttr.name + ' restarted';
|
|
||||||
break;
|
|
||||||
case 'pause':
|
|
||||||
details = 'Container ' + eventAttr.name + ' paused';
|
|
||||||
break;
|
|
||||||
case 'unpause':
|
|
||||||
details = 'Container ' + eventAttr.name + ' unpaused';
|
|
||||||
break;
|
|
||||||
case 'attach':
|
|
||||||
details = 'Container ' + eventAttr.name + ' attached';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (event.Action.indexOf('exec_create') === 0) {
|
|
||||||
details = 'Exec instance created';
|
|
||||||
} else if (event.Action.indexOf('exec_start') === 0) {
|
|
||||||
details = 'Exec instance started';
|
|
||||||
} else {
|
|
||||||
details = 'Unsupported event';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'image':
|
|
||||||
switch (event.Action) {
|
|
||||||
case 'delete':
|
|
||||||
details = 'Image deleted';
|
|
||||||
break;
|
|
||||||
case 'tag':
|
|
||||||
details = 'New tag created for ' + eventAttr.name;
|
|
||||||
break;
|
|
||||||
case 'untag':
|
|
||||||
details = 'Image untagged';
|
|
||||||
break;
|
|
||||||
case 'pull':
|
|
||||||
details = 'Image ' + event.Actor.ID + ' pulled';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
details = 'Unsupported event';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'network':
|
|
||||||
switch (event.Action) {
|
|
||||||
case 'create':
|
|
||||||
details = 'Network ' + eventAttr.name + ' created';
|
|
||||||
break;
|
|
||||||
case 'destroy':
|
|
||||||
details = 'Network ' + eventAttr.name + ' deleted';
|
|
||||||
break;
|
|
||||||
case 'connect':
|
|
||||||
details = 'Container connected to ' + eventAttr.name + ' network';
|
|
||||||
break;
|
|
||||||
case 'disconnect':
|
|
||||||
details = 'Container disconnected from ' + eventAttr.name + ' network';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
details = 'Unsupported event';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'volume':
|
|
||||||
switch (event.Action) {
|
|
||||||
case 'create':
|
|
||||||
details = 'Volume ' + event.Actor.ID + ' created';
|
|
||||||
break;
|
|
||||||
case 'destroy':
|
|
||||||
details = 'Volume ' + event.Actor.ID + ' deleted';
|
|
||||||
break;
|
|
||||||
case 'mount':
|
|
||||||
details = 'Volume ' + event.Actor.ID + ' mounted';
|
|
||||||
break;
|
|
||||||
case 'unmount':
|
|
||||||
details = 'Volume ' + event.Actor.ID + ' unmounted';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
details = 'Unsupported event';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
details = 'Unsupported event';
|
|
||||||
}
|
|
||||||
return details;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EventViewModel(data) {
|
|
||||||
// Type, Action, Actor unavailable in Docker < 1.10
|
|
||||||
this.Time = data.time;
|
|
||||||
if (data.Type) {
|
|
||||||
this.Type = data.Type;
|
|
||||||
this.Details = createEventDetails(data);
|
|
||||||
} else {
|
|
||||||
this.Type = data.status;
|
|
||||||
this.Details = data.from;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"validthis": true,
|
|
||||||
"laxcomma" : true,
|
|
||||||
"laxbreak" : true,
|
|
||||||
"browser" : true,
|
|
||||||
"eqnull" : true,
|
|
||||||
"debug" : true,
|
|
||||||
"devel" : true,
|
|
||||||
"boss" : true,
|
|
||||||
"expr" : true,
|
|
||||||
"asi" : true
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "portainer",
|
"name": "portainer",
|
||||||
"version": "1.11.3",
|
"version": "1.11.4",
|
||||||
"homepage": "https://github.com/portainer/portainer",
|
"homepage": "https://github.com/portainer/portainer",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
|
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
|
||||||
|
|
|
@ -2,7 +2,7 @@ FROM microsoft/windowsservercore
|
||||||
|
|
||||||
COPY dist /
|
COPY dist /
|
||||||
|
|
||||||
VOLUME C:\\ProgramData\\Portainer
|
VOLUME C:\\data
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ FROM microsoft/nanoserver
|
||||||
|
|
||||||
COPY dist /
|
COPY dist /
|
||||||
|
|
||||||
VOLUME C:\\ProgramData\\Portainer
|
VOLUME C:\\data
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"author": "Portainer.io",
|
"author": "Portainer.io",
|
||||||
"name": "portainer",
|
"name": "portainer",
|
||||||
"homepage": "http://portainer.io",
|
"homepage": "http://portainer.io",
|
||||||
"version": "1.11.3",
|
"version": "1.11.4",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@github.com:portainer/portainer.git"
|
"url": "git@github.com:portainer/portainer.git"
|
||||||
|
|
Loading…
Reference in New Issue