mirror of https://github.com/portainer/portainer
commit
b72cce810e
|
@ -3,7 +3,6 @@
|
|||
<img title="portainer" src='http://portainer.io/images/logo_alt.png' />
|
||||
</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")
|
||||
[![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)
|
||||
|
|
|
@ -67,20 +67,41 @@ func (service *EndpointService) Endpoints() ([]portainer.Endpoint, error) {
|
|||
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.
|
||||
func (service *EndpointService) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||
return service.store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(endpointBucketName))
|
||||
|
||||
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)
|
||||
err := storeNewEndpoint(endpoint, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -172,3 +193,23 @@ func (service *EndpointService) DeleteActive() error {
|
|||
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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
|
||||
"os"
|
||||
|
@ -13,8 +15,11 @@ import (
|
|||
type Service struct{}
|
||||
|
||||
const (
|
||||
errInvalidEnpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://")
|
||||
errSocketNotFound = portainer.Error("Unable to locate Unix socket")
|
||||
errInvalidEnpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://")
|
||||
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
|
||||
|
@ -22,17 +27,20 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||
kingpin.Version(version)
|
||||
|
||||
flags := &portainer.CLIFlags{
|
||||
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').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')),
|
||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').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(),
|
||||
Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(),
|
||||
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
||||
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints").String(),
|
||||
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source").Default(defaultSyncInterval).String(),
|
||||
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').String(),
|
||||
NoAuth: kingpin.Flag("no-auth", "Disable authentication").Default(defaultNoAuth).Bool(),
|
||||
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()
|
||||
|
@ -41,13 +49,37 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||
|
||||
// ValidateFlags validates the values of the flags.
|
||||
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
|
||||
}
|
||||
|
||||
if strings.HasPrefix(*flags.Endpoint, "unix://") {
|
||||
socketPath := strings.TrimPrefix(*flags.Endpoint, "unix://")
|
||||
if strings.HasPrefix(endpoint, "unix://") {
|
||||
socketPath := strings.TrimPrefix(endpoint, "unix://")
|
||||
if _, err := os.Stat(socketPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ const (
|
|||
defaultDataDirectory = "/data"
|
||||
defaultAssetsDirectory = "."
|
||||
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||
defaultNoAuth = "false"
|
||||
defaultTLSVerify = "false"
|
||||
defaultTLSCACertPath = "/certs/ca.pem"
|
||||
defaultTLSCertPath = "/certs/cert.pem"
|
||||
defaultTLSKeyPath = "/certs/key.pem"
|
||||
defaultSyncInterval = "60s"
|
||||
)
|
||||
|
|
|
@ -5,8 +5,10 @@ const (
|
|||
defaultDataDirectory = "C:\\data"
|
||||
defaultAssetsDirectory = "."
|
||||
defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||
defaultNoAuth = "false"
|
||||
defaultTLSVerify = "false"
|
||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||
defaultSyncInterval = "60s"
|
||||
)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/bolt"
|
||||
"github.com/portainer/portainer/cli"
|
||||
"github.com/portainer/portainer/cron"
|
||||
"github.com/portainer/portainer/crypto"
|
||||
"github.com/portainer/portainer/file"
|
||||
"github.com/portainer/portainer/http"
|
||||
|
@ -12,7 +13,7 @@ import (
|
|||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
var cli portainer.CLIService = &cli.Service{}
|
||||
flags, err := cli.ParseFlags(portainer.APIVersion)
|
||||
if err != nil {
|
||||
|
@ -23,37 +24,76 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
settings := &portainer.Settings{
|
||||
HiddenLabels: *flags.Labels,
|
||||
Logo: *flags.Logo,
|
||||
}
|
||||
|
||||
fileService, err := file.NewService(*flags.Data, "")
|
||||
func initFileService(dataStorePath string) portainer.FileService {
|
||||
fileService, err := file.NewService(dataStorePath, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return fileService
|
||||
}
|
||||
|
||||
var store = bolt.NewStore(*flags.Data)
|
||||
err = store.Open()
|
||||
func initStore(dataStorePath string) *bolt.Store {
|
||||
var store = bolt.NewStore(dataStorePath)
|
||||
err := store.Open()
|
||||
if err != nil {
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return &endpoints[0]
|
||||
}
|
||||
|
||||
var cryptoService portainer.CryptoService = &crypto.Service{}
|
||||
|
||||
// Initialize the active endpoint from the CLI only if there is no
|
||||
// active endpoint defined yet.
|
||||
var activeEndpoint *portainer.Endpoint
|
||||
if *flags.Endpoint != "" {
|
||||
activeEndpoint, err = store.EndpointService.GetActive()
|
||||
if err == portainer.ErrEndpointNotFound {
|
||||
func initActiveEndpoint(endpointService portainer.EndpointService, flags *portainer.CLIFlags) *portainer.Endpoint {
|
||||
activeEndpoint, err := endpointService.GetActive()
|
||||
if err == portainer.ErrEndpointNotFound {
|
||||
if *flags.Endpoint != "" {
|
||||
activeEndpoint = &portainer.Endpoint{
|
||||
Name: "primary",
|
||||
URL: *flags.Endpoint,
|
||||
|
@ -62,30 +102,54 @@ func main() {
|
|||
TLSCertPath: *flags.TLSCert,
|
||||
TLSKeyPath: *flags.TLSKey,
|
||||
}
|
||||
err = store.EndpointService.CreateEndpoint(activeEndpoint)
|
||||
err = endpointService.CreateEndpoint(activeEndpoint)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if *flags.ExternalEndpoints != "" {
|
||||
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{
|
||||
BindAddress: *flags.Addr,
|
||||
AssetsPath: *flags.Assets,
|
||||
Settings: settings,
|
||||
TemplatesURL: *flags.Templates,
|
||||
UserService: store.UserService,
|
||||
EndpointService: store.EndpointService,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
ActiveEndpoint: activeEndpoint,
|
||||
BindAddress: *flags.Addr,
|
||||
AssetsPath: *flags.Assets,
|
||||
Settings: settings,
|
||||
TemplatesURL: *flags.Templates,
|
||||
AuthDisabled: *flags.NoAuth,
|
||||
EndpointManagement: authorizeEndpointMgmt,
|
||||
UserService: store.UserService,
|
||||
EndpointService: store.EndpointService,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
ActiveEndpoint: activeEndpoint,
|
||||
}
|
||||
|
||||
log.Printf("Starting Portainer on %s", *flags.Addr)
|
||||
err = server.Start()
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
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 {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
authDisabled bool
|
||||
UserService portainer.UserService
|
||||
CryptoService portainer.CryptoService
|
||||
JWTService portainer.JWTService
|
||||
|
@ -26,6 +27,9 @@ const (
|
|||
ErrInvalidCredentialsFormat = portainer.Error("Invalid credentials format")
|
||||
// ErrInvalidCredentials is an error raised when credentials for a user are invalid
|
||||
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.
|
||||
|
@ -44,6 +48,11 @@ func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
}
|
||||
|
||||
if handler.authDisabled {
|
||||
Error(w, ErrAuthDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var req postAuthRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
Error(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
||||
|
|
|
@ -16,13 +16,20 @@ import (
|
|||
// EndpointHandler represents an HTTP API handler for managing Docker endpoints.
|
||||
type EndpointHandler struct {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
EndpointService portainer.EndpointService
|
||||
FileService portainer.FileService
|
||||
server *Server
|
||||
middleWareService *middleWareService
|
||||
Logger *log.Logger
|
||||
authorizeEndpointManagement bool
|
||||
EndpointService portainer.EndpointService
|
||||
FileService portainer.FileService
|
||||
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.
|
||||
func NewEndpointHandler(middleWareService *middleWareService) *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.
|
||||
// /endpoints(?active=true|false)
|
||||
func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *http.Request) {
|
||||
if !handler.authorizeEndpointManagement {
|
||||
Error(w, ErrEndpointManagementDisabled, http.StatusServiceUnavailable, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var req postEndpointsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
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
|
||||
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)
|
||||
id := vars["id"]
|
||||
|
||||
|
@ -262,6 +279,11 @@ type putEndpointsRequest struct {
|
|||
// handleDeleteEndpoint handles DELETE requests on /endpoints/:id
|
||||
// DELETE /endpoints/0 deletes the active endpoint
|
||||
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)
|
||||
id := vars["id"]
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
|
||||
// Service represents a service to manage HTTP middlewares
|
||||
type middleWareService struct {
|
||||
jwtService portainer.JWTService
|
||||
jwtService portainer.JWTService
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
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
|
||||
func (service *middleWareService) middleWareAuthenticate(next http.Handler) http.Handler {
|
||||
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
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
// Get token from the Authorization header
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
Error(w, portainer.ErrUnauthorized, http.StatusUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
if token == "" {
|
||||
Error(w, portainer.ErrUnauthorized, http.StatusUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err := service.jwtService.VerifyToken(token)
|
||||
if err != nil {
|
||||
Error(w, err, http.StatusUnauthorized, nil)
|
||||
return
|
||||
err := service.jwtService.VerifyToken(token)
|
||||
if err != nil {
|
||||
Error(w, err, http.StatusUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
|
|
@ -8,17 +8,19 @@ import (
|
|||
|
||||
// Server implements the portainer.Server interface
|
||||
type Server struct {
|
||||
BindAddress string
|
||||
AssetsPath string
|
||||
UserService portainer.UserService
|
||||
EndpointService portainer.EndpointService
|
||||
CryptoService portainer.CryptoService
|
||||
JWTService portainer.JWTService
|
||||
FileService portainer.FileService
|
||||
Settings *portainer.Settings
|
||||
TemplatesURL string
|
||||
ActiveEndpoint *portainer.Endpoint
|
||||
Handler *Handler
|
||||
BindAddress string
|
||||
AssetsPath string
|
||||
AuthDisabled bool
|
||||
EndpointManagement bool
|
||||
UserService portainer.UserService
|
||||
EndpointService portainer.EndpointService
|
||||
CryptoService portainer.CryptoService
|
||||
JWTService portainer.JWTService
|
||||
FileService portainer.FileService
|
||||
Settings *portainer.Settings
|
||||
TemplatesURL string
|
||||
ActiveEndpoint *portainer.Endpoint
|
||||
Handler *Handler
|
||||
}
|
||||
|
||||
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
|
||||
func (server *Server) Start() error {
|
||||
middleWareService := &middleWareService{
|
||||
jwtService: server.JWTService,
|
||||
jwtService: server.JWTService,
|
||||
authDisabled: server.AuthDisabled,
|
||||
}
|
||||
|
||||
var authHandler = NewAuthHandler()
|
||||
authHandler.UserService = server.UserService
|
||||
authHandler.CryptoService = server.CryptoService
|
||||
authHandler.JWTService = server.JWTService
|
||||
authHandler.authDisabled = server.AuthDisabled
|
||||
var userHandler = NewUserHandler(middleWareService)
|
||||
userHandler.UserService = server.UserService
|
||||
userHandler.CryptoService = server.CryptoService
|
||||
|
@ -58,6 +62,7 @@ func (server *Server) Start() error {
|
|||
var websocketHandler = NewWebSocketHandler()
|
||||
// EndpointHandler requires a reference to the server to be able to update the active endpoint.
|
||||
var endpointHandler = NewEndpointHandler(middleWareService)
|
||||
endpointHandler.authorizeEndpointManagement = server.EndpointManagement
|
||||
endpointHandler.EndpointService = server.EndpointService
|
||||
endpointHandler.FileService = server.FileService
|
||||
endpointHandler.server = server
|
||||
|
|
|
@ -13,23 +13,28 @@ type (
|
|||
|
||||
// CLIFlags represents the available flags on the CLI.
|
||||
CLIFlags struct {
|
||||
Addr *string
|
||||
Assets *string
|
||||
Data *string
|
||||
Endpoint *string
|
||||
Labels *[]Pair
|
||||
Logo *string
|
||||
Templates *string
|
||||
TLSVerify *bool
|
||||
TLSCacert *string
|
||||
TLSCert *string
|
||||
TLSKey *string
|
||||
Addr *string
|
||||
Assets *string
|
||||
Data *string
|
||||
ExternalEndpoints *string
|
||||
SyncInterval *string
|
||||
Endpoint *string
|
||||
Labels *[]Pair
|
||||
Logo *string
|
||||
Templates *string
|
||||
NoAuth *bool
|
||||
TLSVerify *bool
|
||||
TLSCacert *string
|
||||
TLSCert *string
|
||||
TLSKey *string
|
||||
}
|
||||
|
||||
// Settings represents Portainer settings.
|
||||
Settings struct {
|
||||
HiddenLabels []Pair `json:"hiddenLabels"`
|
||||
Logo string `json:"logo"`
|
||||
HiddenLabels []Pair `json:"hiddenLabels"`
|
||||
Logo string `json:"logo"`
|
||||
Authentication bool `json:"authentication"`
|
||||
EndpointManagement bool `json:"endpointManagement"`
|
||||
}
|
||||
|
||||
// User represent a user account.
|
||||
|
@ -95,6 +100,7 @@ type (
|
|||
GetActive() (*Endpoint, error)
|
||||
SetActive(endpoint *Endpoint) error
|
||||
DeleteActive() error
|
||||
Synchronize(toCreate, toUpdate, toDelete []*Endpoint) error
|
||||
}
|
||||
|
||||
// CryptoService represents a service for encrypting/hashing data.
|
||||
|
@ -115,11 +121,16 @@ type (
|
|||
GetPathForTLSFile(endpointID EndpointID, fileType TLSFileType) (string, error)
|
||||
DeleteTLSFiles(endpointID EndpointID) error
|
||||
}
|
||||
|
||||
// EndpointWatcher represents a service to synchronize the endpoints via an external source.
|
||||
EndpointWatcher interface {
|
||||
WatchEndpointFile(endpointFilePath string) error
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of portainer API.
|
||||
APIVersion = "1.11.3"
|
||||
APIVersion = "1.11.4"
|
||||
)
|
||||
|
||||
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', [
|
||||
'portainer.templates',
|
||||
'ui.bootstrap',
|
||||
'ui.router',
|
||||
'ui.select',
|
||||
|
@ -9,9 +12,11 @@ angular.module('portainer', [
|
|||
'angularUtils.directives.dirPagination',
|
||||
'LocalStorageModule',
|
||||
'angular-jwt',
|
||||
'portainer.services',
|
||||
'portainer.helpers',
|
||||
'portainer.templates',
|
||||
'portainer.filters',
|
||||
'portainer.rest',
|
||||
'portainer.helpers',
|
||||
'portainer.services',
|
||||
'auth',
|
||||
'dashboard',
|
||||
'container',
|
||||
|
@ -19,29 +24,29 @@ angular.module('portainer', [
|
|||
'containerLogs',
|
||||
'containers',
|
||||
'createContainer',
|
||||
'createNetwork',
|
||||
'createService',
|
||||
'createVolume',
|
||||
'docker',
|
||||
'endpoint',
|
||||
'endpointInit',
|
||||
'endpoints',
|
||||
'events',
|
||||
'images',
|
||||
'image',
|
||||
'images',
|
||||
'main',
|
||||
'network',
|
||||
'networks',
|
||||
'node',
|
||||
'service',
|
||||
'services',
|
||||
'settings',
|
||||
'sidebar',
|
||||
'createService',
|
||||
'stats',
|
||||
'swarm',
|
||||
'network',
|
||||
'networks',
|
||||
'node',
|
||||
'createNetwork',
|
||||
'task',
|
||||
'templates',
|
||||
'volumes',
|
||||
'createVolume'])
|
||||
'volumes'])
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider) {
|
||||
'use strict';
|
||||
|
||||
|
@ -62,124 +67,121 @@ angular.module('portainer', [
|
|||
$urlRouterProvider.otherwise('/auth');
|
||||
|
||||
$stateProvider
|
||||
.state('root', {
|
||||
abstract: true,
|
||||
resolve: {
|
||||
requiresLogin: ['StateManager', function (StateManager) {
|
||||
var applicationState = StateManager.getState();
|
||||
return applicationState.application.authentication;
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('auth', {
|
||||
url: '/auth',
|
||||
parent: 'root',
|
||||
url: '/auth',
|
||||
params: {
|
||||
logout: false,
|
||||
error: ''
|
||||
},
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/auth/auth.html',
|
||||
controller: 'AuthenticationController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: false
|
||||
}
|
||||
})
|
||||
.state('containers', {
|
||||
parent: 'root',
|
||||
url: '/containers/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/containers/containers.html',
|
||||
controller: 'ContainersController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('container', {
|
||||
url: "^/containers/:id",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/container/container.html',
|
||||
controller: 'ContainerController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('stats', {
|
||||
url: "^/containers/:id/stats",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/stats/stats.html',
|
||||
controller: 'StatsController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('logs', {
|
||||
url: "^/containers/:id/logs",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/containerLogs/containerlogs.html',
|
||||
controller: 'ContainerLogsController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('console', {
|
||||
url: "^/containers/:id/console",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/containerConsole/containerConsole.html',
|
||||
controller: 'ContainerConsoleController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('dashboard', {
|
||||
parent: 'root',
|
||||
url: '/dashboard',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/dashboard/dashboard.html',
|
||||
controller: 'DashboardController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions', {
|
||||
abstract: true,
|
||||
url: "/actions",
|
||||
views: {
|
||||
"content": {
|
||||
template: '<div ui-view="content"></div>'
|
||||
"content@": {
|
||||
template: '<div ui-view="content@"></div>'
|
||||
},
|
||||
"sidebar": {
|
||||
template: '<div ui-view="sidebar"></div>'
|
||||
"sidebar@": {
|
||||
template: '<div ui-view="sidebar@"></div>'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -187,344 +189,281 @@ angular.module('portainer', [
|
|||
abstract: true,
|
||||
url: "/create",
|
||||
views: {
|
||||
"content": {
|
||||
template: '<div ui-view="content"></div>'
|
||||
"content@": {
|
||||
template: '<div ui-view="content@"></div>'
|
||||
},
|
||||
"sidebar": {
|
||||
template: '<div ui-view="sidebar"></div>'
|
||||
"sidebar@": {
|
||||
template: '<div ui-view="sidebar@"></div>'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('actions.create.container', {
|
||||
url: "/container",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.network', {
|
||||
url: "/network",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/createNetwork/createnetwork.html',
|
||||
controller: 'CreateNetworkController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.service', {
|
||||
url: "/service",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/createService/createservice.html',
|
||||
controller: 'CreateServiceController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.volume', {
|
||||
url: "/volume",
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/createVolume/createvolume.html',
|
||||
controller: 'CreateVolumeController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('docker', {
|
||||
url: '/docker/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/docker/docker.html',
|
||||
controller: 'DockerController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('endpoints', {
|
||||
url: '/endpoints/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/endpoints/endpoints.html',
|
||||
controller: 'EndpointsController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('endpoint', {
|
||||
url: '^/endpoints/:id',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/endpoint/endpoint.html',
|
||||
controller: 'EndpointController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('endpointInit', {
|
||||
url: '/init/endpoint',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/endpointInit/endpointInit.html',
|
||||
controller: 'EndpointInitController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('events', {
|
||||
url: '/events/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/events/events.html',
|
||||
controller: 'EventsController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('images', {
|
||||
url: '/images/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/images/images.html',
|
||||
controller: 'ImagesController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('image', {
|
||||
url: '^/images/:id/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/image/image.html',
|
||||
controller: 'ImageController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('networks', {
|
||||
url: '/networks/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/networks/networks.html',
|
||||
controller: 'NetworksController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('network', {
|
||||
url: '^/networks/:id/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/network/network.html',
|
||||
controller: 'NetworkController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('node', {
|
||||
url: '^/nodes/:id/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/node/node.html',
|
||||
controller: 'NodeController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('services', {
|
||||
url: '/services/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/services/services.html',
|
||||
controller: 'ServicesController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('service', {
|
||||
url: '^/service/:id/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/service/service.html',
|
||||
controller: 'ServiceController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('settings', {
|
||||
url: '/settings/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/settings/settings.html',
|
||||
controller: 'SettingsController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('task', {
|
||||
url: '^/task/:id',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/task/task.html',
|
||||
controller: 'TaskController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('templates', {
|
||||
url: '/templates/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('volumes', {
|
||||
url: '/volumes/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/volumes/volumes.html',
|
||||
controller: 'VolumesController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('swarm', {
|
||||
url: '/swarm/',
|
||||
views: {
|
||||
"content": {
|
||||
"content@": {
|
||||
templateUrl: 'app/components/swarm/swarm.html',
|
||||
controller: 'SwarmController'
|
||||
},
|
||||
"sidebar": {
|
||||
"sidebar@": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -545,18 +484,21 @@ angular.module('portainer', [
|
|||
};
|
||||
});
|
||||
}])
|
||||
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'StateManager', function ($rootScope, $state, Authentication, authManager, StateManager) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
authManager.redirectWhenUnauthenticated();
|
||||
|
||||
Authentication.init();
|
||||
StateManager.init();
|
||||
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'StateManager', 'Messages', function ($rootScope, $state, Authentication, authManager, StateManager, Messages) {
|
||||
StateManager.initialize().then(function success(state) {
|
||||
if (state.application.authentication) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
authManager.redirectWhenUnauthenticated();
|
||||
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.$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
|
||||
// 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('TEMPLATES_ENDPOINT', 'api/templates')
|
||||
.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
|
||||
};
|
||||
|
||||
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) {
|
||||
Authentication.logout();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
angular.module('endpoint', [])
|
||||
.controller('EndpointController', ['$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'Messages',
|
||||
function ($scope, $state, $stateParams, $filter, EndpointService, Messages) {
|
||||
|
||||
if (!$scope.applicationState.application.endpointManagement) {
|
||||
$state.go('endpoints');
|
||||
}
|
||||
|
||||
$scope.state = {
|
||||
error: '',
|
||||
uploadInProgress: false
|
||||
|
|
|
@ -8,7 +8,19 @@
|
|||
<rd-header-content>Endpoint management</rd-header-content>
|
||||
</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">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a new endpoint">
|
||||
|
@ -113,7 +125,7 @@
|
|||
</div>
|
||||
</rd-widget-header>
|
||||
<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>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
|
@ -125,7 +137,7 @@
|
|||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th ng-if="applicationState.application.endpointManagement"></th>
|
||||
<th>
|
||||
<a ui-sref="endpoints" ng-click="order('Name')">
|
||||
Name
|
||||
|
@ -147,16 +159,16 @@
|
|||
<span ng-show="sortType == 'TLS' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th ng-if="applicationState.application.endpointManagement"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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>{{ endpoint.URL | stripprotocol }}</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">
|
||||
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</span>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<a ui-sref="docker" ui-sref-active="active">Docker <span class="menu-icon fa fa-th"></span></a>
|
||||
</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>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
angular
|
||||
.module('portainer')
|
||||
.directive('rdHeaderContent', function rdHeaderContent() {
|
||||
.directive('rdHeaderContent', ['Authentication', function rdHeaderContent(Authentication) {
|
||||
var directive = {
|
||||
requires: '^rdHeader',
|
||||
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'
|
||||
};
|
||||
return directive;
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
angular
|
||||
.module('portainer')
|
||||
.directive('rdHeaderTitle', ['$rootScope', function rdHeaderTitle($rootScope) {
|
||||
.directive('rdHeaderTitle', ['Authentication', function rdHeaderTitle(Authentication) {
|
||||
var directive = {
|
||||
requires: '^rdHeader',
|
||||
scope: {
|
||||
title: '@'
|
||||
},
|
||||
link: function (scope, iElement, iAttrs) {
|
||||
scope.username = $rootScope.username;
|
||||
scope.username = Authentication.getCredentials().username;
|
||||
},
|
||||
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'
|
||||
};
|
||||
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",
|
||||
"version": "1.11.3",
|
||||
"version": "1.11.4",
|
||||
"homepage": "https://github.com/portainer/portainer",
|
||||
"authors": [
|
||||
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
|
||||
|
|
|
@ -2,7 +2,7 @@ FROM microsoft/windowsservercore
|
|||
|
||||
COPY dist /
|
||||
|
||||
VOLUME C:\\ProgramData\\Portainer
|
||||
VOLUME C:\\data
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ FROM microsoft/nanoserver
|
|||
|
||||
COPY dist /
|
||||
|
||||
VOLUME C:\\ProgramData\\Portainer
|
||||
VOLUME C:\\data
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "1.11.3",
|
||||
"version": "1.11.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
|
Loading…
Reference in New Issue