1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 22:05:23 +02:00

Merge branch 'release/1.16.5'

This commit is contained in:
Anthony Lapenna 2018-04-02 07:44:28 +10:00
commit a8ee774cf2
113 changed files with 785 additions and 603 deletions

View file

@ -30,9 +30,6 @@ You can have a use Github filters to list these issues:
* intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate * intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate
* advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced * advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced
### Linting
Please check your code using `grunt lint` before submitting your pull requests.
### Commit Message Format ### Commit Message Format

View file

@ -42,20 +42,17 @@ func NewService(dataStorePath, fileStorePath string) (*Service, error) {
fileStorePath: path.Join(dataStorePath, fileStorePath), fileStorePath: path.Join(dataStorePath, fileStorePath),
} }
// Checking if a mount directory exists is broken with Go on Windows. err := os.MkdirAll(dataStorePath, 0755)
// This will need to be reviewed after the issue has been fixed in Go.
// See: https://github.com/portainer/portainer/issues/474
// err := createDirectoryIfNotExist(dataStorePath, 0755)
// if err != nil {
// return nil, err
// }
err := service.createDirectoryInStoreIfNotExist(TLSStorePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = service.createDirectoryInStoreIfNotExist(ComposeStorePath) err = service.createDirectoryInStore(TLSStorePath)
if err != nil {
return nil, err
}
err = service.createDirectoryInStore(ComposeStorePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,14 +73,14 @@ func (service *Service) GetStackProjectPath(stackIdentifier string) string {
// StoreStackFileFromString creates a subfolder in the ComposeStorePath and stores a new file using the content from a string. // StoreStackFileFromString creates a subfolder in the ComposeStorePath and stores a new file using the content from a string.
// It returns the path to the folder where the file is stored. // It returns the path to the folder where the file is stored.
func (service *Service) StoreStackFileFromString(stackIdentifier, stackFileContent string) (string, error) { func (service *Service) StoreStackFileFromString(stackIdentifier, fileName, stackFileContent string) (string, error) {
stackStorePath := path.Join(ComposeStorePath, stackIdentifier) stackStorePath := path.Join(ComposeStorePath, stackIdentifier)
err := service.createDirectoryInStoreIfNotExist(stackStorePath) err := service.createDirectoryInStore(stackStorePath)
if err != nil { if err != nil {
return "", err return "", err
} }
composeFilePath := path.Join(stackStorePath, ComposeFileDefaultName) composeFilePath := path.Join(stackStorePath, fileName)
data := []byte(stackFileContent) data := []byte(stackFileContent)
r := bytes.NewReader(data) r := bytes.NewReader(data)
@ -97,14 +94,14 @@ func (service *Service) StoreStackFileFromString(stackIdentifier, stackFileConte
// StoreStackFileFromReader creates a subfolder in the ComposeStorePath and stores a new file using the content from an io.Reader. // StoreStackFileFromReader creates a subfolder in the ComposeStorePath and stores a new file using the content from an io.Reader.
// It returns the path to the folder where the file is stored. // It returns the path to the folder where the file is stored.
func (service *Service) StoreStackFileFromReader(stackIdentifier string, r io.Reader) (string, error) { func (service *Service) StoreStackFileFromReader(stackIdentifier, fileName string, r io.Reader) (string, error) {
stackStorePath := path.Join(ComposeStorePath, stackIdentifier) stackStorePath := path.Join(ComposeStorePath, stackIdentifier)
err := service.createDirectoryInStoreIfNotExist(stackStorePath) err := service.createDirectoryInStore(stackStorePath)
if err != nil { if err != nil {
return "", err return "", err
} }
composeFilePath := path.Join(stackStorePath, ComposeFileDefaultName) composeFilePath := path.Join(stackStorePath, fileName)
err = service.createFileInStore(composeFilePath, r) err = service.createFileInStore(composeFilePath, r)
if err != nil { if err != nil {
@ -117,7 +114,7 @@ func (service *Service) StoreStackFileFromReader(stackIdentifier string, r io.Re
// StoreTLSFile creates a folder in the TLSStorePath and stores a new file with the content from r. // StoreTLSFile creates a folder in the TLSStorePath and stores a new file with the content from r.
func (service *Service) StoreTLSFile(folder string, fileType portainer.TLSFileType, r io.Reader) error { func (service *Service) StoreTLSFile(folder string, fileType portainer.TLSFileType, r io.Reader) error {
storePath := path.Join(TLSStorePath, folder) storePath := path.Join(TLSStorePath, folder)
err := service.createDirectoryInStoreIfNotExist(storePath) err := service.createDirectoryInStore(storePath)
if err != nil { if err != nil {
return err return err
} }
@ -201,24 +198,10 @@ func (service *Service) GetFileContent(filePath string) (string, error) {
return string(content), nil return string(content), nil
} }
// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system. // createDirectoryInStore creates a new directory in the file store
func (service *Service) createDirectoryInStoreIfNotExist(name string) error { func (service *Service) createDirectoryInStore(name string) error {
path := path.Join(service.fileStorePath, name) path := path.Join(service.fileStorePath, name)
return createDirectoryIfNotExist(path, 0700) return os.MkdirAll(path, 0700)
}
// createDirectoryIfNotExist creates a directory if it doesn't exists on the file system.
func createDirectoryIfNotExist(path string, mode uint32) error {
_, err := os.Stat(path)
if os.IsNotExist(err) {
err = os.Mkdir(path, os.FileMode(mode))
if err != nil {
return err
}
} else if err != nil {
return err
}
return nil
} }
// createFile creates a new file in the file store with the content from r. // createFile creates a new file in the file store with the content from r.

View file

@ -1,6 +1,9 @@
package git package git
import ( import (
"net/url"
"strings"
"gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4"
) )
@ -14,12 +17,23 @@ func NewService(dataStorePath string) (*Service, error) {
return service, nil return service, nil
} }
// CloneRepository clones a git repository using the specified URL in the specified // ClonePublicRepository clones a public git repository using the specified URL in the specified
// destination folder. // destination folder.
func (service *Service) CloneRepository(url, destination string) error { func (service *Service) ClonePublicRepository(repositoryURL, destination string) error {
_, err := git.PlainClone(destination, false, &git.CloneOptions{ return cloneRepository(repositoryURL, destination)
URL: url, }
})
// ClonePrivateRepositoryWithBasicAuth clones a private git repository using the specified URL in the specified
// destination folder. It will use the specified username and password for basic HTTP authentication.
func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error {
credentials := username + ":" + url.PathEscape(password)
repositoryURL = strings.Replace(repositoryURL, "://", "://"+credentials+"@", 1)
return cloneRepository(repositoryURL, destination)
}
func cloneRepository(repositoryURL, destination string) error {
_, err := git.PlainClone(destination, false, &git.CloneOptions{
URL: repositoryURL,
})
return err return err
} }

View file

@ -52,6 +52,8 @@ func (handler *DockerHubHandler) handleGetDockerHub(w http.ResponseWriter, r *ht
return return
} }
dockerhub.Password = ""
encodeJSON(w, dockerhub, handler.Logger) encodeJSON(w, dockerhub, handler.Logger)
return return
} }

View file

@ -52,7 +52,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r) http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/endpoints"): case strings.HasPrefix(r.URL.Path, "/api/endpoints"):
switch { switch {
case strings.Contains(r.URL.Path, "/docker"): case strings.Contains(r.URL.Path, "/docker/"):
http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r) http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r)
case strings.Contains(r.URL.Path, "/stacks"): case strings.Contains(r.URL.Path, "/stacks"):
http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r) http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r)

View file

@ -91,6 +91,10 @@ func (handler *RegistryHandler) handleGetRegistries(w http.ResponseWriter, r *ht
return return
} }
for i := range filteredRegistries {
filteredRegistries[i].Password = ""
}
encodeJSON(w, filteredRegistries, handler.Logger) encodeJSON(w, filteredRegistries, handler.Logger)
} }
@ -159,6 +163,8 @@ func (handler *RegistryHandler) handleGetRegistry(w http.ResponseWriter, r *http
return return
} }
registry.Password = ""
encodeJSON(w, registry, handler.Logger) encodeJSON(w, registry, handler.Logger)
} }

View file

@ -70,12 +70,15 @@ func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
type ( type (
postStacksRequest struct { postStacksRequest struct {
Name string `valid:"required"` Name string `valid:"required"`
SwarmID string `valid:"required"` SwarmID string `valid:"required"`
StackFileContent string `valid:""` StackFileContent string `valid:""`
GitRepository string `valid:""` RepositoryURL string `valid:""`
PathInRepository string `valid:""` RepositoryAuthentication bool `valid:""`
Env []portainer.Pair `valid:""` RepositoryUsername string `valid:""`
RepositoryPassword string `valid:""`
ComposeFilePathInRepository string `valid:""`
Env []portainer.Pair `valid:""`
} }
postStacksResponse struct { postStacksResponse struct {
ID string `json:"Id"` ID string `json:"Id"`
@ -179,7 +182,7 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
Env: req.Env, Env: req.Env,
} }
projectPath, err := handler.FileService.StoreStackFileFromString(string(stack.ID), stackFileContent) projectPath, err := handler.FileService.StoreStackFileFromString(string(stack.ID), stack.EntryPoint, stackFileContent)
if err != nil { if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return return
@ -263,24 +266,20 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
} }
stackName := req.Name stackName := req.Name
if stackName == "" {
httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger)
return
}
swarmID := req.SwarmID swarmID := req.SwarmID
if swarmID == "" {
if stackName == "" || swarmID == "" || req.RepositoryURL == "" {
httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger) httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger)
return return
} }
if req.GitRepository == "" { if req.RepositoryAuthentication && (req.RepositoryUsername == "" || req.RepositoryPassword == "") {
httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger) httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger)
return return
} }
if req.PathInRepository == "" { if req.ComposeFilePathInRepository == "" {
req.PathInRepository = filesystem.ComposeFileDefaultName req.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
} }
stacks, err := handler.StackService.Stacks() stacks, err := handler.StackService.Stacks()
@ -300,7 +299,7 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
ID: portainer.StackID(stackName + "_" + swarmID), ID: portainer.StackID(stackName + "_" + swarmID),
Name: stackName, Name: stackName,
SwarmID: swarmID, SwarmID: swarmID,
EntryPoint: req.PathInRepository, EntryPoint: req.ComposeFilePathInRepository,
Env: req.Env, Env: req.Env,
} }
@ -314,7 +313,11 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
return return
} }
err = handler.GitService.CloneRepository(req.GitRepository, projectPath) if req.RepositoryAuthentication {
err = handler.GitService.ClonePrivateRepositoryWithBasicAuth(req.RepositoryURL, projectPath, req.RepositoryUsername, req.RepositoryPassword)
} else {
err = handler.GitService.ClonePublicRepository(req.RepositoryURL, projectPath)
}
if err != nil { if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return return
@ -431,7 +434,7 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
Env: env, Env: env,
} }
projectPath, err := handler.FileService.StoreStackFileFromReader(string(stack.ID), stackFile) projectPath, err := handler.FileService.StoreStackFileFromReader(string(stack.ID), stack.EntryPoint, stackFile)
if err != nil { if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return return
@ -631,7 +634,7 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque
} }
stack.Env = req.Env stack.Env = req.Env
_, err = handler.FileService.StoreStackFileFromString(string(stack.ID), req.StackFileContent) _, err = handler.FileService.StoreStackFileFromString(string(stack.ID), stack.EntryPoint, req.StackFileContent)
if err != nil { if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return return

View file

@ -15,6 +15,8 @@ type proxyFactory struct {
ResourceControlService portainer.ResourceControlService ResourceControlService portainer.ResourceControlService
TeamMembershipService portainer.TeamMembershipService TeamMembershipService portainer.TeamMembershipService
SettingsService portainer.SettingsService SettingsService portainer.SettingsService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
} }
func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler { func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler {
@ -45,6 +47,8 @@ func (factory *proxyFactory) newDockerSocketProxy(path string) http.Handler {
ResourceControlService: factory.ResourceControlService, ResourceControlService: factory.ResourceControlService,
TeamMembershipService: factory.TeamMembershipService, TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService, SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
dockerTransport: newSocketTransport(path), dockerTransport: newSocketTransport(path),
} }
proxy.Transport = transport proxy.Transport = transport
@ -57,6 +61,8 @@ func (factory *proxyFactory) createDockerReverseProxy(u *url.URL) *httputil.Reve
ResourceControlService: factory.ResourceControlService, ResourceControlService: factory.ResourceControlService,
TeamMembershipService: factory.TeamMembershipService, TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService, SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
dockerTransport: &http.Transport{}, dockerTransport: &http.Transport{},
} }
proxy.Transport = transport proxy.Transport = transport

View file

@ -17,7 +17,7 @@ type Manager struct {
} }
// NewManager initializes a new proxy Service // NewManager initializes a new proxy Service
func NewManager(resourceControlService portainer.ResourceControlService, teamMembershipService portainer.TeamMembershipService, settingsService portainer.SettingsService) *Manager { func NewManager(resourceControlService portainer.ResourceControlService, teamMembershipService portainer.TeamMembershipService, settingsService portainer.SettingsService, registryService portainer.RegistryService, dockerHubService portainer.DockerHubService) *Manager {
return &Manager{ return &Manager{
proxies: cmap.New(), proxies: cmap.New(),
extensionProxies: cmap.New(), extensionProxies: cmap.New(),
@ -25,6 +25,8 @@ func NewManager(resourceControlService portainer.ResourceControlService, teamMem
ResourceControlService: resourceControlService, ResourceControlService: resourceControlService,
TeamMembershipService: teamMembershipService, TeamMembershipService: teamMembershipService,
SettingsService: settingsService, SettingsService: settingsService,
RegistryService: registryService,
DockerHubService: dockerHubService,
}, },
} }
} }

View file

@ -0,0 +1,37 @@
package proxy
import (
"github.com/portainer/portainer"
"github.com/portainer/portainer/http/security"
)
func createRegistryAuthenticationHeader(serverAddress string, accessContext *registryAccessContext) *registryAuthenticationHeader {
var authenticationHeader *registryAuthenticationHeader
if serverAddress == "" {
authenticationHeader = &registryAuthenticationHeader{
Username: accessContext.dockerHub.Username,
Password: accessContext.dockerHub.Password,
Serveraddress: "docker.io",
}
} else {
var matchingRegistry *portainer.Registry
for _, registry := range accessContext.registries {
if registry.URL == serverAddress &&
(accessContext.isAdmin || (!accessContext.isAdmin && security.AuthorizedRegistryAccess(&registry, accessContext.userID, accessContext.teamMemberships))) {
matchingRegistry = &registry
break
}
}
if matchingRegistry != nil {
authenticationHeader = &registryAuthenticationHeader{
Username: matchingRegistry.Username,
Password: matchingRegistry.Password,
Serveraddress: matchingRegistry.URL,
}
}
}
return authenticationHeader
}

View file

@ -1,6 +1,8 @@
package proxy package proxy
import ( import (
"encoding/base64"
"encoding/json"
"net/http" "net/http"
"path" "path"
"strings" "strings"
@ -14,6 +16,8 @@ type (
dockerTransport *http.Transport dockerTransport *http.Transport
ResourceControlService portainer.ResourceControlService ResourceControlService portainer.ResourceControlService
TeamMembershipService portainer.TeamMembershipService TeamMembershipService portainer.TeamMembershipService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
SettingsService portainer.SettingsService SettingsService portainer.SettingsService
} }
restrictedOperationContext struct { restrictedOperationContext struct {
@ -22,6 +26,18 @@ type (
userTeamIDs []portainer.TeamID userTeamIDs []portainer.TeamID
resourceControls []portainer.ResourceControl resourceControls []portainer.ResourceControl
} }
registryAccessContext struct {
isAdmin bool
userID portainer.UserID
teamMemberships []portainer.TeamMembership
registries []portainer.Registry
dockerHub *portainer.DockerHub
}
registryAuthenticationHeader struct {
Username string `json:"username"`
Password string `json:"password"`
Serveraddress string `json:"serveraddress"`
}
operationExecutor struct { operationExecutor struct {
operationContext *restrictedOperationContext operationContext *restrictedOperationContext
labelBlackList []portainer.Pair labelBlackList []portainer.Pair
@ -62,6 +78,8 @@ func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Respon
return p.proxyTaskRequest(request) return p.proxyTaskRequest(request)
case strings.HasPrefix(path, "/build"): case strings.HasPrefix(path, "/build"):
return p.proxyBuildRequest(request) return p.proxyBuildRequest(request)
case strings.HasPrefix(path, "/images"):
return p.proxyImageRequest(request)
default: default:
return p.executeDockerRequest(request) return p.executeDockerRequest(request)
} }
@ -119,7 +137,7 @@ func (p *proxyTransport) proxyContainerRequest(request *http.Request) (*http.Res
func (p *proxyTransport) proxyServiceRequest(request *http.Request) (*http.Response, error) { func (p *proxyTransport) proxyServiceRequest(request *http.Request) (*http.Response, error) {
switch requestPath := request.URL.Path; requestPath { switch requestPath := request.URL.Path; requestPath {
case "/services/create": case "/services/create":
return p.executeDockerRequest(request) return p.replaceRegistryAuthenticationHeader(request)
case "/services": case "/services":
return p.rewriteOperation(request, serviceListOperation) return p.rewriteOperation(request, serviceListOperation)
@ -235,6 +253,54 @@ func (p *proxyTransport) proxyBuildRequest(request *http.Request) (*http.Respons
return p.interceptAndRewriteRequest(request, buildOperation) return p.interceptAndRewriteRequest(request, buildOperation)
} }
func (p *proxyTransport) proxyImageRequest(request *http.Request) (*http.Response, error) {
switch requestPath := request.URL.Path; requestPath {
case "/images/create":
return p.replaceRegistryAuthenticationHeader(request)
default:
if match, _ := path.Match("/images/*/push", requestPath); match {
return p.replaceRegistryAuthenticationHeader(request)
}
return p.executeDockerRequest(request)
}
}
func (p *proxyTransport) replaceRegistryAuthenticationHeader(request *http.Request) (*http.Response, error) {
accessContext, err := p.createRegistryAccessContext(request)
if err != nil {
return nil, err
}
originalHeader := request.Header.Get("X-Registry-Auth")
if originalHeader != "" {
decodedHeaderData, err := base64.StdEncoding.DecodeString(originalHeader)
if err != nil {
return nil, err
}
var originalHeaderData registryAuthenticationHeader
err = json.Unmarshal(decodedHeaderData, &originalHeaderData)
if err != nil {
return nil, err
}
authenticationHeader := createRegistryAuthenticationHeader(originalHeaderData.Serveraddress, accessContext)
headerData, err := json.Marshal(authenticationHeader)
if err != nil {
return nil, err
}
header := base64.StdEncoding.EncodeToString(headerData)
request.Header.Set("X-Registry-Auth", header)
}
return p.executeDockerRequest(request)
}
// restrictedOperation ensures that the current user has the required authorizations // restrictedOperation ensures that the current user has the required authorizations
// before executing the original request. // before executing the original request.
func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) { func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) {
@ -270,7 +336,7 @@ func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID s
return p.executeDockerRequest(request) return p.executeDockerRequest(request)
} }
// rewriteOperation will create a new operation context with data that will be used // rewriteOperationWithLabelFiltering will create a new operation context with data that will be used
// to decorate the original request's response as well as retrieve all the black listed labels // to decorate the original request's response as well as retrieve all the black listed labels
// to filter the resources. // to filter the resources.
func (p *proxyTransport) rewriteOperationWithLabelFiltering(request *http.Request, operation restrictedOperationRequest) (*http.Response, error) { func (p *proxyTransport) rewriteOperationWithLabelFiltering(request *http.Request, operation restrictedOperationRequest) (*http.Response, error) {
@ -341,6 +407,43 @@ func (p *proxyTransport) administratorOperation(request *http.Request) (*http.Re
return p.executeDockerRequest(request) return p.executeDockerRequest(request)
} }
func (p *proxyTransport) createRegistryAccessContext(request *http.Request) (*registryAccessContext, error) {
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
}
accessContext := &registryAccessContext{
isAdmin: true,
userID: tokenData.ID,
}
hub, err := p.DockerHubService.DockerHub()
if err != nil {
return nil, err
}
accessContext.dockerHub = hub
registries, err := p.RegistryService.Registries()
if err != nil {
return nil, err
}
accessContext.registries = registries
if tokenData.Role != portainer.AdministratorRole {
accessContext.isAdmin = false
teamMemberships, err := p.TeamMembershipService.TeamMembershipsByUserID(tokenData.ID)
if err != nil {
return nil, err
}
accessContext.teamMemberships = teamMemberships
}
return accessContext, nil
}
func (p *proxyTransport) createOperationContext(request *http.Request) (*restrictedOperationContext, error) { func (p *proxyTransport) createOperationContext(request *http.Request) (*restrictedOperationContext, error) {
var err error var err error
tokenData, err := security.RetrieveTokenData(request) tokenData, err := security.RetrieveTokenData(request)

View file

@ -140,3 +140,22 @@ func AuthorizedEndpointAccess(endpoint *portainer.Endpoint, userID portainer.Use
} }
return false return false
} }
// AuthorizedRegistryAccess ensure that the user can access the specified registry.
// It will check if the user is part of the authorized users or part of a team that is
// listed in the authorized teams.
func AuthorizedRegistryAccess(registry *portainer.Registry, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
for _, authorizedUserID := range registry.AuthorizedUsers {
if authorizedUserID == userID {
return true
}
}
for _, membership := range memberships {
for _, authorizedTeamID := range registry.AuthorizedTeams {
if membership.TeamID == authorizedTeamID {
return true
}
}
}
return false
}

View file

@ -69,7 +69,7 @@ func FilterRegistries(registries []portainer.Registry, context *RestrictedReques
filteredRegistries = make([]portainer.Registry, 0) filteredRegistries = make([]portainer.Registry, 0)
for _, registry := range registries { for _, registry := range registries {
if isRegistryAccessAuthorized(&registry, context.UserID, context.UserMemberships) { if AuthorizedRegistryAccess(&registry, context.UserID, context.UserMemberships) {
filteredRegistries = append(filteredRegistries, registry) filteredRegistries = append(filteredRegistries, registry)
} }
} }
@ -87,7 +87,7 @@ func FilterEndpoints(endpoints []portainer.Endpoint, context *RestrictedRequestC
filteredEndpoints = make([]portainer.Endpoint, 0) filteredEndpoints = make([]portainer.Endpoint, 0)
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if isEndpointAccessAuthorized(&endpoint, context.UserID, context.UserMemberships) { if AuthorizedEndpointAccess(&endpoint, context.UserID, context.UserMemberships) {
filteredEndpoints = append(filteredEndpoints, endpoint) filteredEndpoints = append(filteredEndpoints, endpoint)
} }
} }
@ -95,35 +95,3 @@ func FilterEndpoints(endpoints []portainer.Endpoint, context *RestrictedRequestC
return filteredEndpoints, nil return filteredEndpoints, nil
} }
func isRegistryAccessAuthorized(registry *portainer.Registry, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
for _, authorizedUserID := range registry.AuthorizedUsers {
if authorizedUserID == userID {
return true
}
}
for _, membership := range memberships {
for _, authorizedTeamID := range registry.AuthorizedTeams {
if membership.TeamID == authorizedTeamID {
return true
}
}
}
return false
}
func isEndpointAccessAuthorized(endpoint *portainer.Endpoint, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
for _, authorizedUserID := range endpoint.AuthorizedUsers {
if authorizedUserID == userID {
return true
}
}
for _, membership := range memberships {
for _, authorizedTeamID := range endpoint.AuthorizedTeams {
if membership.TeamID == authorizedTeamID {
return true
}
}
}
return false
}

View file

@ -42,7 +42,7 @@ type Server struct {
// Start starts the HTTP server // Start starts the HTTP server
func (server *Server) Start() error { func (server *Server) Start() error {
requestBouncer := security.NewRequestBouncer(server.JWTService, server.UserService, server.TeamMembershipService, server.AuthDisabled) requestBouncer := security.NewRequestBouncer(server.JWTService, server.UserService, server.TeamMembershipService, server.AuthDisabled)
proxyManager := proxy.NewManager(server.ResourceControlService, server.TeamMembershipService, server.SettingsService) proxyManager := proxy.NewManager(server.ResourceControlService, server.TeamMembershipService, server.SettingsService, server.RegistryService, server.DockerHubService)
var fileHandler = handler.NewFileHandler(filepath.Join(server.AssetsPath, "public")) var fileHandler = handler.NewFileHandler(filepath.Join(server.AssetsPath, "public"))
var authHandler = handler.NewAuthHandler(requestBouncer, server.AuthDisabled) var authHandler = handler.NewAuthHandler(requestBouncer, server.AuthDisabled)

View file

@ -152,7 +152,7 @@ type (
URL string `json:"URL"` URL string `json:"URL"`
Authentication bool `json:"Authentication"` Authentication bool `json:"Authentication"`
Username string `json:"Username"` Username string `json:"Username"`
Password string `json:"Password"` Password string `json:"Password,omitempty"`
AuthorizedUsers []UserID `json:"AuthorizedUsers"` AuthorizedUsers []UserID `json:"AuthorizedUsers"`
AuthorizedTeams []TeamID `json:"AuthorizedTeams"` AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
} }
@ -162,7 +162,7 @@ type (
DockerHub struct { DockerHub struct {
Authentication bool `json:"Authentication"` Authentication bool `json:"Authentication"`
Username string `json:"Username"` Username string `json:"Username"`
Password string `json:"Password"` Password string `json:"Password,omitempty"`
} }
// EndpointID represents an endpoint identifier. // EndpointID represents an endpoint identifier.
@ -369,13 +369,14 @@ type (
DeleteTLSFile(folder string, fileType TLSFileType) error DeleteTLSFile(folder string, fileType TLSFileType) error
DeleteTLSFiles(folder string) error DeleteTLSFiles(folder string) error
GetStackProjectPath(stackIdentifier string) string GetStackProjectPath(stackIdentifier string) string
StoreStackFileFromString(stackIdentifier string, stackFileContent string) (string, error) StoreStackFileFromString(stackIdentifier, fileName, stackFileContent string) (string, error)
StoreStackFileFromReader(stackIdentifier string, r io.Reader) (string, error) StoreStackFileFromReader(stackIdentifier, fileName string, r io.Reader) (string, error)
} }
// GitService represents a service for managing Git. // GitService represents a service for managing Git.
GitService interface { GitService interface {
CloneRepository(url, destination string) error ClonePublicRepository(repositoryURL, destination string) error
ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error
} }
// EndpointWatcher represents a service to synchronize the endpoints via an external source. // EndpointWatcher represents a service to synchronize the endpoints via an external source.
@ -400,7 +401,7 @@ type (
const ( const (
// APIVersion is the version number of the Portainer API. // APIVersion is the version number of the Portainer API.
APIVersion = "1.16.4" APIVersion = "1.16.5"
// DBVersion is the version number of the Portainer database. // DBVersion is the version number of the Portainer database.
DBVersion = 8 DBVersion = 8
// DefaultTemplatesURL represents the default URL for the templates definitions. // DefaultTemplatesURL represents the default URL for the templates definitions.

View file

@ -56,7 +56,7 @@ info:
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).
version: "1.16.4" version: "1.16.5"
title: "Portainer API" title: "Portainer API"
contact: contact:
email: "info@portainer.io" email: "info@portainer.io"
@ -2143,7 +2143,7 @@ definitions:
description: "Is analytics enabled" description: "Is analytics enabled"
Version: Version:
type: "string" type: "string"
example: "1.16.4" example: "1.16.5"
description: "Portainer API version" description: "Portainer API version"
PublicSettingsInspectResponse: PublicSettingsInspectResponse:
type: "object" type: "object"
@ -2904,14 +2904,26 @@ definitions:
type: "string" type: "string"
example: "version: 3\n services:\n web:\n image:nginx" example: "version: 3\n services:\n web:\n image:nginx"
description: "Content of the Stack file. Required when using the 'string' deployment method." description: "Content of the Stack file. Required when using the 'string' deployment method."
GitRepository: RepositoryURL:
type: "string" type: "string"
example: "https://github.com/openfaas/faas" example: "https://github.com/openfaas/faas"
description: "URL of a public Git repository hosting the Stack file. Required when using the 'repository' deployment method." description: "URL of a Git repository hosting the Stack file. Required when using the 'repository' deployment method."
PathInRepository: ComposeFilePathInRepository:
type: "string" type: "string"
example: "docker-compose.yml" example: "docker-compose.yml"
description: "Path to the Stack file inside the Git repository. Required when using the 'repository' deployment method." description: "Path to the Stack file inside the Git repository. Required when using the 'repository' deployment method."
RepositoryAuthentication:
type: "boolean"
example: true
description: "Use basic authentication to clone the Git repository."
RepositoryUsername:
type: "string"
example: "myGitUsername"
description: "Username used in basic authentication. Required when RepositoryAuthentication is true."
RepositoryPassword:
type: "string"
example: "myGitPassword"
description: "Password used in basic authentication. Required when RepositoryAuthentication is true."
Env: Env:
type: "array" type: "array"
description: "A list of environment variables used during stack deployment" description: "A list of environment variables used during stack deployment"

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.configs.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.configs.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add config <i class="fa fa-plus space-right" aria-hidden="true"></i>Add config
@ -35,22 +35,22 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('CreatedAt')"> <a ng-click="$ctrl.changeOrderBy('CreatedAt')">
Creation Date Creation Date
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -44,7 +44,7 @@
<td>{{ value.MacAddress || '-' }}</td> <td>{{ value.MacAddress || '-' }}</td>
<td> <td>
<button type="button" class="btn btn-xs btn-danger" ng-disabled="$ctrl.leaveNetworkActionInProgress" button-spinner="$ctrl.leaveNetworkActionInProgress" ng-click="$ctrl.leaveNetworkAction($ctrl.container, value.NetworkID)"> <button type="button" class="btn btn-xs btn-danger" ng-disabled="$ctrl.leaveNetworkActionInProgress" button-spinner="$ctrl.leaveNetworkActionInProgress" ng-click="$ctrl.leaveNetworkAction($ctrl.container, value.NetworkID)">
<span ng-hide="$ctrl.leaveNetworkActionInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i> Leave network</span> <span ng-hide="$ctrl.leaveNetworkActionInProgress"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i> Leave network</span>
<span ng-show="$ctrl.leaveNetworkActionInProgress">Leaving network...</span> <span ng-show="$ctrl.leaveNetworkActionInProgress">Leaving network...</span>
</button> </button>
</td> </td>

View file

@ -67,7 +67,7 @@
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.restartAction($ctrl.state.selectedItems)" <button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.restartAction($ctrl.state.selectedItems)"
ng-disabled="$ctrl.state.selectedItemCount === 0"> ng-disabled="$ctrl.state.selectedItemCount === 0">
<i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart <i class="fa fa-sync space-right" aria-hidden="true"></i>Restart
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.pauseAction($ctrl.state.selectedItems)" <button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.pauseAction($ctrl.state.selectedItems)"
ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noRunningItemsSelected"> ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noRunningItemsSelected">
@ -79,7 +79,7 @@
</button> </button>
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.containers.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.containers.new">
@ -101,15 +101,15 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Names')"> <a ng-click="$ctrl.changeOrderBy('Names')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open"> <th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open">
<a ng-click="$ctrl.changeOrderBy('Status')"> <a ng-click="$ctrl.changeOrderBy('Status')">
State State
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
</a> </a>
<div> <div>
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.state.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span> <span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.state.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
@ -138,43 +138,43 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('StackName')"> <a ng-click="$ctrl.changeOrderBy('StackName')">
Stack Stack
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Image')"> <a ng-click="$ctrl.changeOrderBy('Image')">
Image Image
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('IP')"> <a ng-click="$ctrl.changeOrderBy('IP')">
IP Address IP Address
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.swarmContainers"> <th ng-if="$ctrl.swarmContainers">
<a ng-click="$ctrl.changeOrderBy('Host')"> <a ng-click="$ctrl.changeOrderBy('Host')">
Host IP Host IP
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Ports')"> <a ng-click="$ctrl.changeOrderBy('Ports')">
Published Ports Published Ports
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>
@ -195,8 +195,8 @@
</td> </td>
<td ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect"> <td ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect">
<div class="btn-group btn-group-xs" role="group" aria-label="..." style="display:inline-flex;"> <div class="btn-group btn-group-xs" role="group" aria-label="..." style="display:inline-flex;">
<a ng-if="$ctrl.settings.showQuickActionStats" style="margin: 0 2.5px;" ui-sref="docker.containers.container.stats({id: item.Id})" title="Stats"><i class="fa fa-area-chart space-right" aria-hidden="true"></i></a> <a ng-if="$ctrl.settings.showQuickActionStats" style="margin: 0 2.5px;" ui-sref="docker.containers.container.stats({id: item.Id})" title="Stats"><i class="fa fa-chart-area space-right" aria-hidden="true"></i></a>
<a ng-if="$ctrl.settings.showQuickActionLogs" style="margin: 0 2.5px;" ui-sref="docker.containers.container.logs({id: item.Id})" title="Logs"><i class="fa fa-file-text-o space-right" aria-hidden="true"></i></a> <a ng-if="$ctrl.settings.showQuickActionLogs" style="margin: 0 2.5px;" ui-sref="docker.containers.container.logs({id: item.Id})" title="Logs"><i class="fa fa-file-alt space-right" aria-hidden="true"></i></a>
<a ng-if="$ctrl.settings.showQuickActionConsole" style="margin: 0 2.5px;" ui-sref="docker.containers.container.console({id: item.Id})" title="Console"><i class="fa fa-terminal space-right" aria-hidden="true"></i></a> <a ng-if="$ctrl.settings.showQuickActionConsole" style="margin: 0 2.5px;" ui-sref="docker.containers.container.console({id: item.Id})" title="Console"><i class="fa fa-terminal space-right" aria-hidden="true"></i></a>
<a ng-if="$ctrl.settings.showQuickActionInspect" style="margin: 0 2.5px;" ui-sref="docker.containers.container.inspect({id: item.Id})" title="Inspect"><i class="fa fa-info-circle space-right" aria-hidden="true"></i></a> <a ng-if="$ctrl.settings.showQuickActionInspect" style="margin: 0 2.5px;" ui-sref="docker.containers.container.inspect({id: item.Id})" title="Inspect"><i class="fa fa-info-circle space-right" aria-hidden="true"></i></a>
</div> </div>
@ -207,7 +207,7 @@
<td ng-if="$ctrl.swarmContainers">{{ item.hostIP }}</td> <td ng-if="$ctrl.swarmContainers">{{ item.hostIP }}</td>
<td> <td>
<a ng-if="item.Ports.length > 0" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl || p.host }}:{{p.public}}" target="_blank"> <a ng-if="item.Ports.length > 0" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl || p.host }}:{{p.public}}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.public }}:{{ p.private }} <i class="fa fa-external-link-alt" aria-hidden="true"></i> {{ p.public }}:{{ p.private }}
</a> </a>
<span ng-if="item.Ports.length == 0" >-</span> <span ng-if="item.Ports.length == 0" >-</span>
</td> </td>

View file

@ -22,22 +22,22 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Time')"> <a ng-click="$ctrl.changeOrderBy('Time')">
Date Date
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Type')"> <a ng-click="$ctrl.changeOrderBy('Type')">
Category Category
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Details')"> <a ng-click="$ctrl.changeOrderBy('Details')">
Details Details
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -15,7 +15,7 @@
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems, false)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems, false)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="$ctrl.state.selectedItemCount === 0"> <button type="button" class="btn btn-sm btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="$ctrl.state.selectedItemCount === 0">
<span class="caret"></span> <span class="caret"></span>
@ -44,8 +44,8 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Id')"> <a ng-click="$ctrl.changeOrderBy('Id')">
Id Id
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
</a> </a>
<div> <div>
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span> <span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
@ -75,22 +75,22 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('RepoTags')"> <a ng-click="$ctrl.changeOrderBy('RepoTags')">
Tags Tags
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('VirtualSize')"> <a ng-click="$ctrl.changeOrderBy('VirtualSize')">
Size Size
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Created')"> <a ng-click="$ctrl.changeOrderBy('Created')">
Created Created
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.networks.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.networks.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add network <i class="fa fa-plus space-right" aria-hidden="true"></i>Add network
@ -35,57 +35,57 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('StackName')"> <a ng-click="$ctrl.changeOrderBy('StackName')">
Stack Stack
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Scope')"> <a ng-click="$ctrl.changeOrderBy('Scope')">
Scope Scope
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Driver')"> <a ng-click="$ctrl.changeOrderBy('Driver')">
Driver Driver
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('IPAM.Driver')"> <a ng-click="$ctrl.changeOrderBy('IPAM.Driver')">
IPAM Driver IPAM Driver
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Subnet')"> <a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Subnet')">
IPAM Subnet IPAM Subnet
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Gateway')"> <a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Gateway')">
IPAM Gateway IPAM Gateway
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -22,36 +22,36 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Id')"> <a ng-click="$ctrl.changeOrderBy('Id')">
Id Id
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Status')"> <a ng-click="$ctrl.changeOrderBy('Status')">
Status Status
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Slot')"> <a ng-click="$ctrl.changeOrderBy('Slot')">
Slot Slot
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Spec.ContainerSpec.Image')"> <a ng-click="$ctrl.changeOrderBy('Spec.ContainerSpec.Image')">
Image Image
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Updated')"> <a ng-click="$ctrl.changeOrderBy('Updated')">
Last Update Last Update
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -22,50 +22,50 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Hostname')"> <a ng-click="$ctrl.changeOrderBy('Hostname')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Role')"> <a ng-click="$ctrl.changeOrderBy('Role')">
Role Role
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('CPUs')"> <a ng-click="$ctrl.changeOrderBy('CPUs')">
CPU CPU
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Memory')"> <a ng-click="$ctrl.changeOrderBy('Memory')">
Memory Memory
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('EngineVersion')"> <a ng-click="$ctrl.changeOrderBy('EngineVersion')">
Engine Engine
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showIpAddressColumn"> <th ng-if="$ctrl.showIpAddressColumn">
<a ng-click="$ctrl.changeOrderBy('Addr')"> <a ng-click="$ctrl.changeOrderBy('Addr')">
IP Address IP Address
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Status')"> <a ng-click="$ctrl.changeOrderBy('Status')">
Status Status
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -3,7 +3,7 @@
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<div class="toolBar"> <div class="toolBar">
<div class="toolBarTitle"> <div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }} <i class="fas" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
</div> </div>
<div class="settings"> <div class="settings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter"> <span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
@ -22,43 +22,43 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('name')"> <a ng-click="$ctrl.changeOrderBy('name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('cpu')"> <a ng-click="$ctrl.changeOrderBy('cpu')">
CPU CPU
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('memory')"> <a ng-click="$ctrl.changeOrderBy('memory')">
Memory Memory
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('ip')"> <a ng-click="$ctrl.changeOrderBy('ip')">
IP Address IP Address
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('version')"> <a ng-click="$ctrl.changeOrderBy('version')">
Engine Engine
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('status')"> <a ng-click="$ctrl.changeOrderBy('status')">
Status Status
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.secrets.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.secrets.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret <i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret
@ -35,22 +35,22 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('CreatedAt')"> <a ng-click="$ctrl.changeOrderBy('CreatedAt')">
Creation Date Creation Date
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -15,11 +15,11 @@
<div class="btn-group" role="group" aria-label="..."> <div class="btn-group" role="group" aria-label="...">
<button ng-if="$ctrl.showForceUpdateButton" type="button" class="btn btn-sm btn-primary" <button ng-if="$ctrl.showForceUpdateButton" type="button" class="btn btn-sm btn-primary"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.forceUpdateAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.forceUpdateAction($ctrl.state.selectedItems)">
<i class="fa fa-refresh space-right" aria-hidden="true"></i>Update <i class="fa fa-sync space-right" aria-hidden="true"></i>Update
</button> </button>
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.services.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.services.new">
@ -41,50 +41,50 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('StackName')"> <a ng-click="$ctrl.changeOrderBy('StackName')">
Stack Stack
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Image')"> <a ng-click="$ctrl.changeOrderBy('Image')">
Image Image
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Mode')"> <a ng-click="$ctrl.changeOrderBy('Mode')">
Scheduling Mode Scheduling Mode
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Ports')"> <a ng-click="$ctrl.changeOrderBy('Ports')">
Published Ports Published Ports
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('UpdatedAt')"> <a ng-click="$ctrl.changeOrderBy('UpdatedAt')">
Last Update Last Update
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>
@ -104,18 +104,18 @@
{{ item.Mode }} <code>{{ item.Running }}</code> / <code>{{ item.Replicas }}</code> {{ item.Mode }} <code>{{ item.Running }}</code> / <code>{{ item.Replicas }}</code>
<span ng-if="item.Mode === 'replicated' && !item.Scale"> <span ng-if="item.Mode === 'replicated' && !item.Scale">
<a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas;"> <a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas;">
<i class="fa fa-arrows-v" aria-hidden="true"></i> Scale <i class="fa fa-arrows-alt-v" aria-hidden="true"></i> Scale
</a> </a>
</span> </span>
<span ng-if="item.Mode === 'replicated' && item.Scale"> <span ng-if="item.Mode === 'replicated' && item.Scale">
<input class="input-sm" type="number" ng-model="item.Replicas" on-enter-key="$ctrl.scaleAction(item)" /> <input class="input-sm" type="number" ng-model="item.Replicas" on-enter-key="$ctrl.scaleAction(item)" />
<a class="interactive" ng-click="item.Scale = false;"><i class="fa fa-times"></i></a> <a class="interactive" ng-click="item.Scale = false;"><i class="fa fa-times"></i></a>
<a class="interactive" ng-click="$ctrl.scaleAction(item)"><i class="fa fa-check-square-o"></i></a> <a class="interactive" ng-click="$ctrl.scaleAction(item)"><i class="fa fa-check-square"></i></a>
</span> </span>
</td> </td>
<td> <td>
<a ng-if="item.Ports && item.Ports.length > 0 && p.PublishedPort" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl }}:{{ p.PublishedPort }}" target="_blank"> <a ng-if="item.Ports && item.Ports.length > 0 && p.PublishedPort" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl }}:{{ p.PublishedPort }}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }} <i class="fa fa-external-link-alt" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
</a> </a>
<span ng-if="!item.Ports || item.Ports.length === 0 || !$ctrl.swarmManagerIp" >-</span> <span ng-if="!item.Ports || item.Ports.length === 0 || !$ctrl.swarmManagerIp" >-</span>
</td> </td>

View file

@ -22,36 +22,36 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Id')"> <a ng-click="$ctrl.changeOrderBy('Id')">
Id Id
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Status')"> <a ng-click="$ctrl.changeOrderBy('Status')">
Status Status
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showSlotColumn"> <th ng-if="$ctrl.showSlotColumn">
<a ng-click="$ctrl.changeOrderBy('Slot')"> <a ng-click="$ctrl.changeOrderBy('Slot')">
Slot Slot
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('NodeId')"> <a ng-click="$ctrl.changeOrderBy('NodeId')">
Node Node
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Updated')"> <a ng-click="$ctrl.changeOrderBy('Updated')">
Last Update Last Update
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showLogsButton">Actions</th> <th ng-if="$ctrl.showLogsButton">Actions</th>
@ -66,7 +66,7 @@
<td>{{ item.Updated | getisodate }}</td> <td>{{ item.Updated | getisodate }}</td>
<td ng-if="$ctrl.showLogsButton"> <td ng-if="$ctrl.showLogsButton">
<a ui-sref="docker.tasks.task.logs({id: item.Id})"> <a ui-sref="docker.tasks.task.logs({id: item.Id})">
<i class="fa fa-file-text-o" aria-hidden="true"></i> View logs <i class="fa fa-file-alt" aria-hidden="true"></i> View logs
</a> </a>
</td> </td>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.volumes.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.volumes.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume <i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume
@ -35,8 +35,8 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Id')"> <a ng-click="$ctrl.changeOrderBy('Id')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
</a> </a>
<div> <div>
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span> <span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
@ -66,29 +66,29 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('StackName')"> <a ng-click="$ctrl.changeOrderBy('StackName')">
Stack Stack
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Driver')"> <a ng-click="$ctrl.changeOrderBy('Driver')">
Driver Driver
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Mountpoint')"> <a ng-click="$ctrl.changeOrderBy('Mountpoint')">
Mount point Mount point
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -1,5 +1,5 @@
<li class="sidebar-list"> <li class="sidebar-list">
<a ui-sref="docker.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer fa-fw"></span></a> <a ui-sref="docker.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
</li> </li>
<li class="sidebar-list"> <li class="sidebar-list">
<a ui-sref="docker.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a> <a ui-sref="docker.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
@ -26,7 +26,7 @@
<a ui-sref="docker.volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes fa-fw"></span></a> <a ui-sref="docker.volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes fa-fw"></span></a>
</li> </li>
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.30 && $ctrl.swarmManagement"> <li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.30 && $ctrl.swarmManagement">
<a ui-sref="docker.configs" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code-o fa-fw"></span></a> <a ui-sref="docker.configs" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code fa-fw"></span></a>
</li> </li>
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement"> <li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement">
<a ui-sref="docker.secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret fa-fw"></span></a> <a ui-sref="docker.secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret fa-fw"></span></a>

View file

@ -3,6 +3,7 @@ angular.module('portainer.docker').component('logViewer', {
controller: 'LogViewerController', controller: 'LogViewerController',
bindings: { bindings: {
data: '=', data: '=',
displayTimestamps: '=',
logCollectionChange: '<' logCollectionChange: '<'
} }
}); });

View file

@ -1,7 +1,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-file-text-o" title="Log viewer settings"></rd-widget-header> <rd-widget-header icon="fa-file-alt" title="Log viewer settings"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
@ -15,6 +15,16 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Display timestamps
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.displayTimestamps"><i></i>
</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="logs_search" class="col-sm-1 control-label text-left"> <label for="logs_search" class="col-sm-1 control-label text-left">
Search Search

View file

@ -1,4 +1,5 @@
function ImageLayerViewModel(data) { function ImageLayerViewModel(order, data) {
this.Order = order;
this.Id = data.Id; this.Id = data.Id;
this.Created = data.Created; this.Created = data.Created;
this.CreatedBy = data.CreatedBy; this.CreatedBy = data.CreatedBy;

View file

@ -57,8 +57,10 @@ angular.module('portainer.docker')
deferred.reject({ msg: data.message }); deferred.reject({ msg: data.message });
} else { } else {
var layers = []; var layers = [];
var order = data.length;
angular.forEach(data, function(imageLayer) { angular.forEach(data, function(imageLayer) {
layers.push(new ImageLayerViewModel(imageLayer)); layers.push(new ImageLayerViewModel(order, imageLayer));
order--;
}); });
deferred.resolve(layers); deferred.resolve(layers);
} }

View file

@ -124,7 +124,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic
return deferred.promise; return deferred.promise;
}; };
service.createStackFromGitRepository = function(name, gitRepository, pathInRepository, env) { service.createStackFromGitRepository = function(name, repositoryOptions, env) {
var deferred = $q.defer(); var deferred = $q.defer();
SwarmService.swarm() SwarmService.swarm()
@ -133,8 +133,11 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic
var payload = { var payload = {
Name: name, Name: name,
SwarmID: swarm.Id, SwarmID: swarm.Id,
GitRepository: gitRepository, RepositoryURL: repositoryOptions.RepositoryURL,
PathInRepository: pathInRepository, ComposeFilePathInRepository: repositoryOptions.ComposeFilePathInRepository,
RepositoryAuthentication: repositoryOptions.RepositoryAuthentication,
RepositoryUsername: repositoryOptions.RepositoryUsername,
RepositoryPassword: repositoryOptions.RepositoryPassword,
Env: env Env: env
}; };
return Stack.create({ method: 'repository' }, payload).$promise; return Stack.create({ method: 'repository' }, payload).$promise;

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Configs list"> <rd-header-title title="Configs list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.configs" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.configs" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Configs</rd-header-content> <rd-header-content>Configs</rd-header-content>
@ -10,7 +10,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<configs-datatable <configs-datatable
title="Configs" title-icon="fa-file-code-o" title="Configs" title-icon="fa-file-code"
dataset="configs" table-key="configs" dataset="configs" table-key="configs"
order-by="Name" show-text-filter="true" order-by="Name" show-text-filter="true"
show-ownership-column="applicationState.application.authentication" show-ownership-column="applicationState.application.authentication"

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Config details"> <rd-header-title title="Config details">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.configs.config({id: config.Id})" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.configs.config({id: config.Id})" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-file-code-o" title="Config details"></rd-widget-header> <rd-widget-header icon="fa-file-code" title="Config details"></rd-widget-header>
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table class="table"> <table class="table">
<tbody> <tbody>
@ -24,7 +24,7 @@
<td>ID</td> <td>ID</td>
<td> <td>
{{ config.Id }} {{ config.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeConfig(config.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this config</button> <button class="btn btn-xs btn-danger" ng-click="removeConfig(config.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this config</button>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -65,7 +65,7 @@
<div class="row" ng-if="config"> <div class="row" ng-if="config">
<div class="col-sm-12"> <div class="col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-file-code-o" title="Config content"></rd-widget-header> <rd-widget-header icon="fa-file-code" title="Config content"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">

View file

@ -18,8 +18,8 @@
<div class="col-lg-11 col-sm-10"> <div class="col-lg-11 col-sm-10">
<div class="input-group" ng-if="!formValues.isCustomCommand"> <div class="input-group" ng-if="!formValues.isCustomCommand">
<span class="input-group-addon"> <span class="input-group-addon">
<i class="fa fa-linux" aria-hidden="true" ng-if="imageOS == 'linux'"></i> <i class="fab fa-linux" aria-hidden="true" ng-if="imageOS == 'linux'"></i>
<i class="fa fa-windows" aria-hidden="true" ng-if="imageOS == 'windows'"></i> <i class="fab fa-windows" aria-hidden="true" ng-if="imageOS == 'windows'"></i>
</span> </span>
<select class="form-control" ng-model="formValues.command" id="command"> <select class="form-control" ng-model="formValues.command" id="command">
<option value="bash" ng-if="imageOS == 'linux'">/bin/bash</option> <option value="bash" ng-if="imageOS == 'linux'">/bin/bash</option>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Container list"> <rd-header-title title="Container list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.containers" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.containers" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Containers</rd-header-content> <rd-header-content>Containers</rd-header-content>

View file

@ -79,7 +79,7 @@
</div> </div>
<!-- !host-port --> <!-- !host-port -->
<span style="margin: 0 10px 0 10px;"> <span style="margin: 0 10px 0 10px;">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
</span> </span>
<!-- container-port --> <!-- container-port -->
<div class="input-group col-sm-4 input-group-sm"> <div class="input-group col-sm-4 input-group-sm">
@ -250,7 +250,7 @@
<!-- !volume-line1 --> <!-- !volume-line1 -->
<!-- volume-line2 --> <!-- volume-line2 -->
<div class="col-sm-12 form-inline" style="margin-top: 5px;"> <div class="col-sm-12 form-inline" style="margin-top: 5px;">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
<!-- volume --> <!-- volume -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'"> <div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'">
<span class="input-group-addon">volume</span> <span class="input-group-addon">volume</span>

View file

@ -15,17 +15,17 @@
<button class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button> <button class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button> <button class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button> <button class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button> <button class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-sync space-right" aria-hidden="true"></i>Restart</button>
<button class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button> <button class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button> <button class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button> <button class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button>
</div> </div>
<div class="btn-group" role="group" aria-label="..."> <div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"> <button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress" ng-if="!container.Config.Labels['com.docker.swarm.service.id']">
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</span> <span ng-hide="state.recreateContainerInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span> <span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
</button> </button>
<button class="btn btn-primary btn-sm" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button> <button class="btn btn-primary btn-sm" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-copy space-right" aria-hidden="true"></i>Duplicate/Edit</button>
</div> </div>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
@ -53,7 +53,7 @@
<form ng-submit="renameContainer()"> <form ng-submit="renameContainer()">
<input type="text" class="containerNameInput" ng-model="container.newContainerName"> <input type="text" class="containerNameInput" ng-model="container.newContainerName">
<a href="" ng-click="container.edit = false;"><i class="fa fa-times"></i></a> <a href="" ng-click="container.edit = false;"><i class="fa fa-times"></i></a>
<a href="" ng-click="renameContainer()"><i class="fa fa-check-square-o"></i></a> <a href="" ng-click="renameContainer()"><i class="fa fa-check-square"></i></a>
</form> </form>
</td> </td>
</tr> </tr>
@ -84,8 +84,8 @@
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="btn-group" role="group" aria-label="..."> <div class="btn-group" role="group" aria-label="...">
<a class="btn" type="button" ui-sref="docker.containers.container.stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a> <a class="btn" type="button" ui-sref="docker.containers.container.stats({id: container.Id})"><i class="fa fa-chart-area space-right" aria-hidden="true"></i>Stats</a>
<a class="btn" type="button" ui-sref="docker.containers.container.logs({id: container.Id})"><i class="fa fa-file-text-o space-right" aria-hidden="true"></i>Logs</a> <a class="btn" type="button" ui-sref="docker.containers.container.logs({id: container.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Logs</a>
<a class="btn" type="button" ui-sref="docker.containers.container.console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a> <a class="btn" type="button" ui-sref="docker.containers.container.console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a class="btn" type="button" ui-sref="docker.containers.container.inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a> <a class="btn" type="button" ui-sref="docker.containers.container.inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
</div> </div>
@ -190,7 +190,7 @@
<td>Port configuration</td> <td>Port configuration</td>
<td> <td>
<div ng-repeat="portMapping in portBindings"> <div ng-repeat="portMapping in portBindings">
{{ portMapping.container }} <i class="fa fa-long-arrow-right"></i> {{ portMapping.host }} {{ portMapping.container }} <i class="fa fa-long-arrow-alt-right"></i> {{ portMapping.host }}
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -12,7 +12,7 @@
<rd-widget-header icon="fa-icon-circle" title="Inspect"> <rd-widget-header icon="fa-icon-circle" title="Inspect">
<span class="btn-group btn-group-sm"> <span class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="state.DisplayTextView" uib-btn-radio="false"><i class="fa fa-code space-right" aria-hidden="true"></i>Tree</label> <label class="btn btn-primary" ng-model="state.DisplayTextView" uib-btn-radio="false"><i class="fa fa-code space-right" aria-hidden="true"></i>Tree</label>
<label class="btn btn-primary" ng-model="state.DisplayTextView" uib-btn-radio="true"><i class="fa fa-file-text-o space-right" aria-hidden="true"></i>Text</label> <label class="btn btn-primary" ng-model="state.DisplayTextView" uib-btn-radio="true"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Text</label>
</span> </span>
</rd-widget-header> </rd-widget-header>
<rd-widget-body> <rd-widget-body>

View file

@ -3,7 +3,8 @@ angular.module('portainer.docker')
function ($scope, $transition$, $interval, ContainerService, Notifications) { function ($scope, $transition$, $interval, ContainerService, Notifications) {
$scope.state = { $scope.state = {
refreshRate: 3, refreshRate: 3,
lineCount: 2000 lineCount: 2000,
displayTimestamps: false
}; };
$scope.changeLogCollection = function(logCollectionStatus) { $scope.changeLogCollection = function(logCollectionStatus) {
@ -33,7 +34,7 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
function setUpdateRepeater() { function setUpdateRepeater() {
var refreshRate = $scope.state.refreshRate; var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() { $scope.repeater = $interval(function() {
ContainerService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
}) })
@ -45,7 +46,7 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
} }
function startLogPolling() { function startLogPolling() {
ContainerService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
setUpdateRepeater(); setUpdateRepeater();

View file

@ -6,5 +6,5 @@
</rd-header> </rd-header>
<log-viewer <log-viewer
data="logs" ng-if="logs" log-collection-change="changeLogCollection" data="logs" ng-if="logs" log-collection-change="changeLogCollection" display-timestamps="state.displayTimestamps"
></log-viewer> ></log-viewer>

View file

@ -52,7 +52,7 @@
<div class="row"> <div class="row">
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]"> <div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-area-chart" title="Memory usage"></rd-widget-header> <rd-widget-header icon="fa-chart-area" title="Memory usage"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="memoryChart" width="770" height="300"></canvas> <canvas id="memoryChart" width="770" height="300"></canvas>
@ -62,7 +62,7 @@
</div> </div>
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]"> <div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-area-chart" title="CPU usage"></rd-widget-header> <rd-widget-header icon="fa-chart-area" title="CPU usage"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="cpuChart" width="770" height="300"></canvas> <canvas id="cpuChart" width="770" height="300"></canvas>
@ -72,7 +72,7 @@
</div> </div>
<div class="col-lg-4 col-md-12 col-sm-12" ng-if="!state.networkStatsUnavailable"> <div class="col-lg-4 col-md-12 col-sm-12" ng-if="!state.networkStatsUnavailable">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-area-chart" title="Network usage"></rd-widget-header> <rd-widget-header icon="fa-chart-area" title="Network usage"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="networkChart" width="770" height="300"></canvas> <canvas id="networkChart" width="770" height="300"></canvas>

View file

@ -6,7 +6,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'"> <div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-tachometer" title="Node info"></rd-widget-header> <rd-widget-header icon="fa-tachometer-alt" title="Node info"></rd-widget-header>
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table class="table"> <table class="table">
<tbody> <tbody>
@ -33,7 +33,7 @@
</div> </div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"> <div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-tachometer" title="Cluster info"></rd-widget-header> <rd-widget-header icon="fa-tachometer-alt" title="Cluster info"></rd-widget-header>
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table class="table"> <table class="table">
<tbody> <tbody>
@ -60,7 +60,7 @@
</div> </div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"> <div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-tachometer" title="Swarm info"></rd-widget-header> <rd-widget-header icon="fa-tachometer-alt" title="Swarm info"></rd-widget-header>
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table class="table"> <table class="table">
<tbody> <tbody>
@ -141,7 +141,7 @@
<i class="fa fa-clone"></i> <i class="fa fa-clone"></i>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<div><i class="fa fa-pie-chart space-right"></i>{{ imageData.size|humansize }}</div> <div><i class="fa fa-chart-pie space-right"></i>{{ imageData.size|humansize }}</div>
</div> </div>
<div class="title">{{ imageData.total }}</div> <div class="title">{{ imageData.total }}</div>
<div class="comment">Images</div> <div class="comment">Images</div>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Engine overview"> <rd-header-title title="Engine overview">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.engine" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.engine" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Docker</rd-header-content> <rd-header-content>Docker</rd-header-content>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Event list"> <rd-header-title title="Event list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.events" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.events" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Events</rd-header-content> <rd-header-content>Events</rd-header-content>

View file

@ -217,7 +217,7 @@
</uib-tab> </uib-tab>
<uib-tab index="1" disable="!buildLogs"> <uib-tab index="1" disable="!buildLogs">
<uib-tab-heading> <uib-tab-heading>
<i class="fa fa-file-text space-right" aria-hidden="true"></i> Output <i class="fa fa-file-alt space-right" aria-hidden="true"></i> Output
</uib-tab-heading> </uib-tab-heading>
<pre class="log_viewer"> <pre class="log_viewer">
<div ng-repeat="line in buildLogs track by $index" class="line"><p class="inner_line" ng-click="active=!active" ng-class="{'line_selected': active}">{{ line }}</p></div> <div ng-repeat="line in buildLogs track by $index" class="line"><p class="inner_line" ng-click="active=!active" ng-class="{'line_selected': active}">{{ line }}</p></div>

View file

@ -24,7 +24,7 @@
<span class="fa fa-download white-icon" aria-hidden="true"></span> <span class="fa fa-download white-icon" aria-hidden="true"></span>
</a> </a>
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)"> <a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)">
<span class="fa fa-trash-o white-icon" aria-hidden="true"></span> <span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
</a> </a>
</span> </span>
</div> </div>
@ -36,17 +36,17 @@
<span class="small text-muted"> <span class="small text-muted">
Note: you can click on the upload icon <span class="fa fa-upload" aria-hidden="true"></span> to push an image Note: you can click on the upload icon <span class="fa fa-upload" aria-hidden="true"></span> to push an image
or on the download icon <span class="fa fa-download" aria-hidden="true"></span> to pull an image or on the download icon <span class="fa fa-download" aria-hidden="true"></span> to pull an image
or on the trash icon <span class="fa fa-trash-o" aria-hidden="true"></span> to delete a tag. or on the trash icon <span class="fa fa-trash-alt" aria-hidden="true"></span> to delete a tag.
</span> </span>
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<span id="downloadResourceHint" class="createResource" style="display: none; margin-left: 0;"> <span id="downloadResourceHint" class="createResource" style="display: none; margin-left: 0;">
Download in progress... Download in progress...
<i class="fa fa-circle-o-notch fa-spin" aria-hidden="true" style="margin-left: 2px;"></i> <i class="fa fa-circle-notch fa-spin" aria-hidden="true" style="margin-left: 2px;"></i>
</span> </span>
<span id="uploadResourceHint" class="createResource" style="display: none; margin-left: 0;"> <span id="uploadResourceHint" class="createResource" style="display: none; margin-left: 0;">
Upload in progress... Upload in progress...
<i class="fa fa-circle-o-notch fa-spin" aria-hidden="true" style="margin-left: 2px;"></i> <i class="fa fa-circle-notch fa-spin" aria-hidden="true" style="margin-left: 2px;"></i>
</span> </span>
</div> </div>
</div> </div>
@ -96,7 +96,7 @@
<td>ID</td> <td>ID</td>
<td> <td>
{{ image.Id }} {{ image.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this image</button> <button class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this image</button>
</td> </td>
</tr> </tr>
<tr ng-if="image.Parent"> <tr ng-if="image.Parent">
@ -182,6 +182,13 @@
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table id="image-layers" class="table"> <table id="image-layers" class="table">
<thead> <thead>
<th style="white-space:nowrap;">
<a ng-click="order('Order')">
Order
<span ng-show="sortType == 'Order' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Order' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th> <th>
<a ng-click="order('Size')"> <a ng-click="order('Size')">
Size Size
@ -199,6 +206,9 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="layer in history | orderBy:sortType:sortReverse"> <tr ng-repeat="layer in history | orderBy:sortType:sortReverse">
<td style="white-space:nowrap;">
{{ layer.Order }}
</td>
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
{{ layer.Size | humansize }} {{ layer.Size | humansize }}
</td> </td>

View file

@ -6,8 +6,8 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ
Registry: '' Registry: ''
}; };
$scope.sortType = 'Size'; $scope.sortType = 'Order';
$scope.sortReverse = true; $scope.sortReverse = false;
$scope.order = function(sortType) { $scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Image list"> <rd-header-title title="Image list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.images" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.images" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Images</rd-header-content> <rd-header-content>Images</rd-header-content>

View file

@ -20,7 +20,7 @@
<td>ID</td> <td>ID</td>
<td> <td>
{{ network.Id }} {{ network.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this network</button> <button class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this network</button>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -94,7 +94,7 @@
<td>{{ container.IPv6Address || '-' }}</td> <td>{{ container.IPv6Address || '-' }}</td>
<td>{{ container.MacAddress || '-' }}</td> <td>{{ container.MacAddress || '-' }}</td>
<td> <td>
<button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(network, container.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Leave Network</button> <button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(network, container.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Leave Network</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Network list"> <rd-header-title title="Network list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.networks" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.networks" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Networks</rd-header-content> <rd-header-content>Networks</rd-header-content>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Node details"> <rd-header-title title="Node details">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.nodes.node({id: node.Id})" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.nodes.node({id: node.Id})" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Secret details"> <rd-header-title title="Secret details">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.secrets.secret({id: secret.Id})" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.secrets.secret({id: secret.Id})" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -24,7 +24,7 @@
<td>ID</td> <td>ID</td>
<td> <td>
{{ secret.Id }} {{ secret.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeSecret(secret.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this secret</button> <button class="btn btn-xs btn-danger" ng-click="removeSecret(secret.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this secret</button>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Secrets list"> <rd-header-title title="Secrets list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.secrets" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.secrets" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Secrets</rd-header-content> <rd-header-content>Secrets</rd-header-content>

View file

@ -70,7 +70,7 @@
</div> </div>
<!-- !host-port --> <!-- !host-port -->
<span style="margin: 0 10px 0 10px;"> <span style="margin: 0 10px 0 10px;">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
</span> </span>
<!-- container-port --> <!-- container-port -->
<div class="input-group col-sm-3 input-group-sm"> <div class="input-group col-sm-3 input-group-sm">
@ -292,7 +292,7 @@
<!-- !volume-line1 --> <!-- !volume-line1 -->
<!-- volume-line2 --> <!-- volume-line2 -->
<div class="col-sm-12 form-inline" style="margin-top: 5px;"> <div class="col-sm-12 form-inline" style="margin-top: 5px;">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
<!-- volume --> <!-- volume -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.Type === 'volume'"> <div class="input-group input-group-sm col-sm-6" ng-if="volume.Type === 'volume'">
<span class="input-group-addon">volume</span> <span class="input-group-addon">volume</span>

View file

@ -14,7 +14,7 @@
<table class="table" > <table class="table" >
<thead> <thead>
<tr> <tr>
<th>Type</th> <th ng-if="isAdmin || allowBindMounts">Type</th>
<th>Source</th> <th>Source</th>
<th>Target</th> <th>Target</th>
<th>Read only</th> <th>Read only</th>
@ -23,14 +23,17 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="mount in service.ServiceMounts"> <tr ng-repeat="mount in service.ServiceMounts">
<td> <td ng-if="isAdmin || allowBindMounts">
<select name="mountType" class="form-control" ng-model="mount.Type" ng-disabled="isUpdating"> <select name="mountType" class="form-control" ng-model="mount.Type" ng-disabled="isUpdating">
<option value="volume">Volume</option> <option value="volume">Volume</option>
<option value="bind">Bind</option> <option value="bind">Bind</option>
</select> </select>
</td> </td>
<td> <td>
<input type="text" class="form-control" ng-model="mount.Source" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating"> <select class="form-control" ng-model="mount.Source" ng-options="vol.Id|truncate:30 as vol.Id for vol in availableVolumes" ng-if="mount.Type === 'volume'">
<option selected disabled hidden value="">Select a volume</option>
</select>
<input type="text" class="form-control" ng-model="mount.Source" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating || (!isAdmin && !allowBindMounts && mount.Type === 'bind')" ng-if="mount.Type === 'bind'">
</td> </td>
<td> <td>
<input type="text" class="form-control" ng-model="mount.Target" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating"> <input type="text" class="form-control" ng-model="mount.Target" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating">

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Service details"> <rd-header-title title="Service details">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.services.service({id: service.Id})" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.services.service({id: service.Id})" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -73,13 +73,13 @@
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<a ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-text-o space-right" aria-hidden="true"></i>Service logs</a> <a ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Service logs</a>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25"> <button type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
<span ng-hide="state.updateInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Update the service</span> <span ng-hide="state.updateInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Update the service</span>
<span ng-show="state.updateInProgress">Update in progress...</span> <span ng-show="state.updateInProgress">Update in progress...</span>
</button> </button>
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress"> <button type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress">
<span ng-hide="state.deletionInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete the service</span> <span ng-hide="state.deletionInProgress"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete the service</span>
<span ng-show="state.deletionInProgress">Deletion in progress...</span> <span ng-show="state.deletionInProgress">Deletion in progress...</span>
</button> </button>
</td> </td>

View file

@ -1,6 +1,6 @@
angular.module('portainer.docker') angular.module('portainer.docker')
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'ModalService', 'PluginService', .controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'ModalService', 'PluginService', 'Authentication', 'SettingsService', 'VolumeService',
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, ModalService, PluginService) { function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, ModalService, PluginService, Authentication, SettingsService, VolumeService) {
$scope.state = { $scope.state = {
updateInProgress: false, updateInProgress: false,
@ -402,6 +402,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
service.RestartDelay = ServiceHelper.translateNanosToHumanDuration(service.RestartDelay) || '5s'; service.RestartDelay = ServiceHelper.translateNanosToHumanDuration(service.RestartDelay) || '5s';
service.RestartWindow = ServiceHelper.translateNanosToHumanDuration(service.RestartWindow) || '0s'; service.RestartWindow = ServiceHelper.translateNanosToHumanDuration(service.RestartWindow) || '0s';
service.UpdateDelay = ServiceHelper.translateNanosToHumanDuration(service.UpdateDelay) || '0s'; service.UpdateDelay = ServiceHelper.translateNanosToHumanDuration(service.UpdateDelay) || '0s';
service.StopGracePeriod = service.StopGracePeriod ? ServiceHelper.translateNanosToHumanDuration(service.StopGracePeriod) : '';
} }
function initView() { function initView() {
@ -422,12 +423,14 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
originalService = angular.copy(service); originalService = angular.copy(service);
return $q.all({ return $q.all({
volumes: VolumeService.volumes(),
tasks: TaskService.tasks({ service: [service.Name] }), tasks: TaskService.tasks({ service: [service.Name] }),
nodes: NodeService.nodes(), nodes: NodeService.nodes(),
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [], secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
configs: apiVersion >= 1.30 ? ConfigService.configs() : [], configs: apiVersion >= 1.30 ? ConfigService.configs() : [],
availableImages: ImageService.images(), availableImages: ImageService.images(),
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25) availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25),
settings: SettingsService.publicSettings()
}); });
}) })
.then(function success(data) { .then(function success(data) {
@ -437,6 +440,10 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
$scope.secrets = data.secrets; $scope.secrets = data.secrets;
$scope.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages); $scope.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
$scope.availableLoggingDrivers = data.availableLoggingDrivers; $scope.availableLoggingDrivers = data.availableLoggingDrivers;
$scope.availableVolumes = data.volumes;
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
var userDetails = Authentication.getUserDetails();
$scope.isAdmin = userDetails.role === 1;
// Set max cpu value // Set max cpu value
var maxCpus = 0; var maxCpus = 0;

View file

@ -3,7 +3,8 @@ angular.module('portainer.docker')
function ($scope, $transition$, $interval, ServiceService, Notifications) { function ($scope, $transition$, $interval, ServiceService, Notifications) {
$scope.state = { $scope.state = {
refreshRate: 3, refreshRate: 3,
lineCount: 2000 lineCount: 2000,
displayTimestamps: false
}; };
$scope.changeLogCollection = function(logCollectionStatus) { $scope.changeLogCollection = function(logCollectionStatus) {
@ -29,7 +30,7 @@ function ($scope, $transition$, $interval, ServiceService, Notifications) {
function setUpdateRepeater() { function setUpdateRepeater() {
var refreshRate = $scope.state.refreshRate; var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() { $scope.repeater = $interval(function() {
ServiceService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) ServiceService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
}) })
@ -41,7 +42,7 @@ function ($scope, $transition$, $interval, ServiceService, Notifications) {
} }
function startLogPolling() { function startLogPolling() {
ServiceService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) ServiceService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
setUpdateRepeater(); setUpdateRepeater();

View file

@ -6,5 +6,5 @@
</rd-header> </rd-header>
<log-viewer <log-viewer
data="logs" ng-if="logs" log-collection-change="changeLogCollection" data="logs" ng-if="logs" log-collection-change="changeLogCollection" display-timestamps="state.displayTimestamps"
></log-viewer> ></log-viewer>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Service list"> <rd-header-title title="Service list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.services" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.services" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Services</rd-header-content> <rd-header-content>Services</rd-header-content>

View file

@ -7,8 +7,11 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid
StackFileContent: '', StackFileContent: '',
StackFile: null, StackFile: null,
RepositoryURL: '', RepositoryURL: '',
RepositoryAuthentication: false,
RepositoryUsername: '',
RepositoryPassword: '',
Env: [], Env: [],
RepositoryPath: 'docker-compose.yml', ComposeFilePathInRepository: 'docker-compose.yml',
AccessControlData: new AccessControlFormData() AccessControlData: new AccessControlFormData()
}; };
@ -48,9 +51,14 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid
var stackFile = $scope.formValues.StackFile; var stackFile = $scope.formValues.StackFile;
return StackService.createStackFromFileUpload(name, stackFile, env); return StackService.createStackFromFileUpload(name, stackFile, env);
} else if (method === 'repository') { } else if (method === 'repository') {
var gitRepository = $scope.formValues.RepositoryURL; var repositoryOptions = {
var pathInRepository = $scope.formValues.RepositoryPath; RepositoryURL: $scope.formValues.RepositoryURL,
return StackService.createStackFromGitRepository(name, gitRepository, pathInRepository, env); ComposeFilePathInRepository: $scope.formValues.ComposeFilePathInRepository,
RepositoryAuthentication: $scope.formValues.RepositoryAuthentication,
RepositoryUsername: $scope.formValues.RepositoryUsername,
RepositoryPassword: $scope.formValues.RepositoryPassword
};
return StackService.createStackFromGitRepository(name, repositoryOptions, env);
} }
} }
@ -74,20 +82,15 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid
$scope.state.actionInProgress = true; $scope.state.actionInProgress = true;
createStack(name, method) createStack(name, method)
.then(function success(data) {
Notifications.success('Stack successfully deployed');
})
.catch(function error(err) {
Notifications.warning('Deployment error', err.err.data.err);
})
.then(function success(data) { .then(function success(data) {
return ResourceControlService.applyResourceControl('stack', name, userId, accessControlData, []); return ResourceControlService.applyResourceControl('stack', name, userId, accessControlData, []);
}) })
.then(function success() { .then(function success() {
Notifications.success('Stack successfully deployed');
$state.go('docker.stacks'); $state.go('docker.stacks');
}) })
.catch(function error(err) { .catch(function error(err) {
Notifications.error('Failure', err, 'Unable to apply resource control on the stack'); Notifications.warning('Deployment error', err.err.data.err);
}) })
.finally(function final() { .finally(function final() {
$scope.state.actionInProgress = false; $scope.state.actionInProgress = false;

View file

@ -54,7 +54,7 @@
<input type="radio" id="method_repository" ng-model="state.Method" value="repository"> <input type="radio" id="method_repository" ng-model="state.Method" value="repository">
<label for="method_repository"> <label for="method_repository">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-git" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fab fa-git" aria-hidden="true" style="margin-right: 2px;"></i>
Repository Repository
</div> </div>
<p>Use a git repository</p> <p>Use a git repository</p>
@ -113,7 +113,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<span class="col-sm-12 text-muted small"> <span class="col-sm-12 text-muted small">
You can use the URL of a public git repository. You can use the URL of a git repository.
</span> </span>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -130,7 +130,29 @@
<div class="form-group"> <div class="form-group">
<label for="stack_repository_path" class="col-sm-2 control-label text-left">Compose path</label> <label for="stack_repository_path" class="col-sm-2 control-label text-left">Compose path</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" ng-model="formValues.RepositoryPath" id="stack_repository_path" placeholder="docker-compose.yml"> <input type="text" class="form-control" ng-model="formValues.ComposeFilePathInRepository" id="stack_repository_path" placeholder="docker-compose.yml">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label class="control-label text-left">
Authentication
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="formValues.RepositoryAuthentication"><i></i>
</label>
</div>
</div>
<div class="form-group" ng-if="formValues.RepositoryAuthentication">
<label for="repository_username" class="col-sm-1 control-label text-left">Username</label>
<div class="col-sm-11 col-md-5">
<input type="text" class="form-control" ng-model="formValues.RepositoryUsername" name="repository_username" placeholder="myGitUser">
</div>
<label for="repository_password" class="col-sm-1 margin-sm-top control-label text-left">
Password
</label>
<div class="col-sm-11 col-md-5 margin-sm-top">
<input type="password" class="form-control" ng-model="formValues.RepositoryPassword" name="repository_password" placeholder="myPassword">
</div> </div>
</div> </div>
</div> </div>
@ -174,7 +196,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress <button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress
|| (state.Method === 'upload' && !formValues.StackFile) || (state.Method === 'upload' && !formValues.StackFile)
|| (state.Method === 'repository' && (!formValues.RepositoryURL || !formValues.RepositoryPath)) || (state.Method === 'repository' && ((!formValues.RepositoryURL || !formValues.ComposeFilePathInRepository) || (formValues.RepositoryAuthentication && (!formValues.RepositoryUsername || !formValues.RepositoryPassword))))
|| !formValues.Name" ng-click="deployStack()" button-spinner="state.actionInProgress"> || !formValues.Name" ng-click="deployStack()" button-spinner="state.actionInProgress">
<span ng-hide="state.actionInProgress">Deploy the stack</span> <span ng-hide="state.actionInProgress">Deploy the stack</span>
<span ng-show="state.actionInProgress">Deployment in progress...</span> <span ng-show="state.actionInProgress">Deployment in progress...</span>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Stack details"> <rd-header-title title="Stack details">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks.stack({id: stack.Id})" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks.stack({id: stack.Id})" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -48,7 +48,7 @@
<div class="row" ng-if="stackFileContent"> <div class="row" ng-if="stackFileContent">
<div class="col-sm-12"> <div class="col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-pencil" title="Stack editor"></rd-widget-header> <rd-widget-header icon="fa-pencil-alt" title="Stack editor"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Stacks list"> <rd-header-title title="Stacks list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Stacks</rd-header-content> <rd-header-content>Stacks</rd-header-content>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Cluster overview"> <rd-header-title title="Cluster overview">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.swarm" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.swarm" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Swarm</rd-header-content> <rd-header-content>Swarm</rd-header-content>
@ -74,14 +74,14 @@
<div class="row"> <div class="row">
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"> <div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<nodes-ss-datatable <nodes-ss-datatable
title="Nodes" title-icon="fa-hdd-o" title="Nodes" title-icon="fa-hdd"
dataset="swarm.Status" table-key="nodes" dataset="swarm.Status" table-key="nodes"
order-by="name" show-text-filter="true" order-by="name" show-text-filter="true"
></nodes-ss-datatable> ></nodes-ss-datatable>
</div> </div>
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"> <div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<nodes-datatable <nodes-datatable
title="Nodes" title-icon="fa-hdd-o" title="Nodes" title-icon="fa-hdd"
dataset="nodes" table-key="nodes" dataset="nodes" table-key="nodes"
order-by="Hostname" show-text-filter="true" order-by="Hostname" show-text-filter="true"
show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25" show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25"

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Swarm visualizer"> <rd-header-title title="Swarm visualizer">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.swarm.visualizer" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.swarm.visualizer" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -86,8 +86,8 @@
<div> <div>
<b>{{ node.Hostname }}</b> <b>{{ node.Hostname }}</b>
<span class="node_platform"> <span class="node_platform">
<i class="fa fa-linux" aria-hidden="true" ng-if="node.PlatformOS === 'linux'"></i> <i class="fab fa-linux" aria-hidden="true" ng-if="node.PlatformOS === 'linux'"></i>
<i class="fa fa-windows" aria-hidden="true" ng-if="node.PlatformOS === 'windows'"></i> <i class="fab fa-windows" aria-hidden="true" ng-if="node.PlatformOS === 'windows'"></i>
</span> </span>
</div> </div>
</div> </div>

View file

@ -45,7 +45,7 @@
<td>{{ task.Status.ContainerStatus.ContainerID }}</td> <td>{{ task.Status.ContainerStatus.ContainerID }}</td>
</tr> </tr>
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30" > <tr ng-if="applicationState.endpoint.apiVersion >= 1.30" >
<td colspan="2"><a class="btn btn-primary btn-sm" type="button" ui-sref="docker.tasks.task.logs({id: task.Id})"><i class="fa fa-file-text-o space-right" aria-hidden="true"></i>Task logs</a></td> <td colspan="2"><a class="btn btn-primary btn-sm" type="button" ui-sref="docker.tasks.task.logs({id: task.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Task logs</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -3,7 +3,8 @@ angular.module('portainer.docker')
function ($scope, $transition$, $interval, TaskService, ServiceService, Notifications) { function ($scope, $transition$, $interval, TaskService, ServiceService, Notifications) {
$scope.state = { $scope.state = {
refreshRate: 3, refreshRate: 3,
lineCount: 2000 lineCount: 2000,
displayTimestamps: false
}; };
$scope.changeLogCollection = function(logCollectionStatus) { $scope.changeLogCollection = function(logCollectionStatus) {
@ -29,7 +30,7 @@ function ($scope, $transition$, $interval, TaskService, ServiceService, Notifica
function setUpdateRepeater() { function setUpdateRepeater() {
var refreshRate = $scope.state.refreshRate; var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() { $scope.repeater = $interval(function() {
TaskService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) TaskService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
}) })
@ -41,7 +42,7 @@ function ($scope, $transition$, $interval, TaskService, ServiceService, Notifica
} }
function startLogPolling() { function startLogPolling() {
TaskService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) TaskService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
setUpdateRepeater(); setUpdateRepeater();

View file

@ -6,5 +6,5 @@
</rd-header> </rd-header>
<log-viewer <log-viewer
data="logs" ng-if="logs" log-collection-change="changeLogCollection" data="logs" ng-if="logs" log-collection-change="changeLogCollection" display-timestamps="state.displayTimestamps"
></log-viewer> ></log-viewer>

View file

@ -1,7 +1,7 @@
<rd-header id="view-top"> <rd-header id="view-top">
<rd-header-title title="Application templates list"> <rd-header-title title="Application templates list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.templates" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.templates" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Templates</rd-header-content> <rd-header-content>Templates</rd-header-content>
@ -173,7 +173,7 @@
</div> </div>
<!-- !host-port --> <!-- !host-port -->
<span style="margin: 0 10px 0 10px;"> <span style="margin: 0 10px 0 10px;">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
</span> </span>
<!-- container-port --> <!-- container-port -->
<div class="input-group col-sm-4 input-group-sm"> <div class="input-group col-sm-4 input-group-sm">
@ -234,7 +234,7 @@
<!-- !volume-line1 --> <!-- !volume-line1 -->
<!-- volume-line2 --> <!-- volume-line2 -->
<div class="col-sm-12 form-inline" style="margin-top: 5px;" ng-if="volume.type !== 'auto'"> <div class="col-sm-12 form-inline" style="margin-top: 5px;" ng-if="volume.type !== 'auto'">
<i class="fa fa-long-arrow-right" aria-hidden="true"></i> <i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
<!-- volume --> <!-- volume -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'"> <div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'">
<span class="input-group-addon">volume</span> <span class="input-group-addon">volume</span>
@ -365,11 +365,11 @@
All All
</label> </label>
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'windows'"> <label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'windows'">
<i class="fa fa-windows" aria-hidden="true"></i> <i class="fab fa-windows" aria-hidden="true"></i>
Windows Windows
</label> </label>
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'linux'"> <label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'linux'">
<i class="fa fa-linux" aria-hidden="true"></i> <i class="fab fa-linux" aria-hidden="true"></i>
Linux Linux
</label> </label>
</span> </span>
@ -432,8 +432,8 @@
{{ tpl.Title }} {{ tpl.Title }}
</span> </span>
<span> <span>
<i class="fa fa-windows" aria-hidden="true" ng-if="tpl.Platform === 'windows'"></i> <i class="fab fa-windows" aria-hidden="true" ng-if="tpl.Platform === 'windows'"></i>
<i class="fa fa-linux" aria-hidden="true" ng-if="tpl.Platform === 'linux'"></i> <i class="fab fa-linux" aria-hidden="true" ng-if="tpl.Platform === 'linux'"></i>
<!-- Arch / Platform --> <!-- Arch / Platform -->
</span> </span>
</div> </div>

View file

@ -108,18 +108,21 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
} }
} }
StackService.createStackFromGitRepository(stackName, template.Repository.url, template.Repository.stackfile, template.Env) var repositoryOptions = {
.then(function success() { RepositoryURL: template.Repository.url,
Notifications.success('Stack successfully created'); ComposeFilePathInRepository: template.Repository.stackfile
}) };
.catch(function error(err) {
Notifications.warning('Deployment error', err.err.data.err); StackService.createStackFromGitRepository(stackName, repositoryOptions, template.Env)
})
.then(function success(data) { .then(function success(data) {
return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []); return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []);
}) })
.then(function success() { .then(function success() {
$state.go('docker.stacks', {}, {reload: true}); Notifications.success('Stack successfully deployed');
$state.go('docker.stacks');
})
.catch(function error(err) {
Notifications.warning('Deployment error', err.err.data.err);
}) })
.finally(function final() { .finally(function final() {
$scope.state.actionInProgress = false; $scope.state.actionInProgress = false;

View file

@ -16,7 +16,7 @@
<td>ID</td> <td>ID</td>
<td> <td>
{{ volume.Id }} {{ volume.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeVolume()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove this volume</button> <button class="btn btn-xs btn-danger" ng-click="removeVolume()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove this volume</button>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Volume list"> <rd-header-title title="Volume list">
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.volumes" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="docker.volumes" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Volumes</rd-header-content> <rd-header-content>Volumes</rd-header-content>

View file

@ -22,29 +22,29 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Time')"> <a ng-click="$ctrl.changeOrderBy('Time')">
Date Date
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Category')"> <a ng-click="$ctrl.changeOrderBy('Category')">
Category Category
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Module')"> <a ng-click="$ctrl.changeOrderBy('Module')">
Module Module
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Content')"> <a ng-click="$ctrl.changeOrderBy('Content')">
Content Content
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -22,29 +22,29 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('IP')"> <a ng-click="$ctrl.changeOrderBy('IP')">
IP Address IP Address
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Role')"> <a ng-click="$ctrl.changeOrderBy('Role')">
Role Role
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Status')"> <a ng-click="$ctrl.changeOrderBy('Status')">
Status Status
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter"> <div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
@ -32,8 +32,8 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Storidge cluster"> <rd-header-title title="Storidge cluster">
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.cluster" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="storidge.cluster" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -41,7 +41,7 @@
<span ng-show="state.shutdownInProgress">Shutting down cluster...</span> <span ng-show="state.shutdownInProgress">Shutting down cluster...</span>
</button> </button>
<button type="button" class="btn btn-danger btn-sm" ng-click="rebootCluster()" ng-disabled="state.rebootInProgress" button-spinner="state.shutdownInProgress"> <button type="button" class="btn btn-danger btn-sm" ng-click="rebootCluster()" ng-disabled="state.rebootInProgress" button-spinner="state.shutdownInProgress">
<span ng-hide="state.rebootInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i> Reboot the cluster</span> <span ng-hide="state.rebootInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i> Reboot the cluster</span>
<span ng-show="state.rebootInProgress">Rebooting cluster...</span> <span ng-show="state.rebootInProgress">Rebooting cluster...</span>
</button> </button>
</div> </div>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Storidge monitor"> <rd-header-title title="Storidge monitor">
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.monitor" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="storidge.monitor" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-tachometer" title="Cluster capacity"></rd-widget-header> <rd-widget-header icon="fa-tachometer-alt" title="Cluster capacity"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="capacityChart" width="770" height="400"></canvas> <canvas id="capacityChart" width="770" height="400"></canvas>
@ -45,7 +45,7 @@
</div> </div>
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-area-chart" title="IOPS usage"></rd-widget-header> <rd-widget-header icon="fa-chart-area" title="IOPS usage"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="iopsChart" width="770" height="400"></canvas> <canvas id="iopsChart" width="770" height="400"></canvas>
@ -78,7 +78,7 @@
</div> </div>
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-area-chart" title="Bandwith usage"></rd-widget-header> <rd-widget-header icon="fa-chart-area" title="Bandwith usage"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="chart-container" style="position: relative;"> <div class="chart-container" style="position: relative;">
<canvas id="bandwithChart" width="770" height="400"></canvas> <canvas id="bandwithChart" width="770" height="400"></canvas>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Storidge profiles"> <rd-header-title title="Storidge profiles">
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.profiles" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="storidge.profiles" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content> <rd-header-content>
@ -49,7 +49,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<storidge-profiles-datatable <storidge-profiles-datatable
title="Profiles" title-icon="fa-sticky-note-o" title="Profiles" title-icon="fa-sticky-note"
dataset="profiles" table-key="storidge_profiles" dataset="profiles" table-key="storidge_profiles"
order-by="Name" show-text-filter="true" order-by="Name" show-text-filter="true"
remove-action="removeAction" remove-action="removeAction"
@ -61,7 +61,7 @@
<!-- <div class="row"> <!-- <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-sticky-note-o" title="Profiles"> <rd-widget-header icon="fa-sticky-note" title="Profiles">
<div class="pull-right"> <div class="pull-right">
Items per page: Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()"> <select ng-model="state.pagination_count" ng-change="changePaginationCount()">
@ -75,7 +75,7 @@
</rd-widget-header> </rd-widget-header>
<rd-widget-taskbar classes="col-md-12"> <rd-widget-taskbar classes="col-md-12">
<div class="pull-left"> <div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeProfiles()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button> <button type="button" class="btn btn-danger" ng-click="removeProfiles()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" /> <input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />

View file

@ -6,7 +6,7 @@ angular.module('portainer.app')
spinning: '=buttonSpinner' spinning: '=buttonSpinner'
}, },
transclude: true, transclude: true,
template: '<ng-transclude></ng-transclude><span ng-show="spinning"><i class="fa fa-circle-o-notch fa-spin" style="margin-left: 2px;"></i>&nbsp;</span>' template: '<ng-transclude></ng-transclude><span ng-show="spinning"><i class="fa fa-circle-notch fa-spin" style="margin-left: 2px;"></i>&nbsp;</span>'
}; };
return directive; return directive;

View file

@ -14,7 +14,7 @@
<div class="actionBar" ng-if="$ctrl.endpointManagement"> <div class="actionBar" ng-if="$ctrl.endpointManagement">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter"> <div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
@ -32,15 +32,15 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('URL')"> <a ng-click="$ctrl.changeOrderBy('URL')">
URL URL
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th>Actions</th> <th>Actions</th>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.registries.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.registries.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry <i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry
@ -35,15 +35,15 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('URL')"> <a ng-click="$ctrl.changeOrderBy('URL')">
URL URL
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th>Actions</th> <th>Actions</th>

View file

@ -22,36 +22,36 @@
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Image')"> <a ng-click="$ctrl.changeOrderBy('Image')">
Image Image
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Mode')"> <a ng-click="$ctrl.changeOrderBy('Mode')">
Scheduling Mode Scheduling Mode
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('Ports')"> <a ng-click="$ctrl.changeOrderBy('Ports')">
Published Ports Published Ports
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('UpdatedAt')"> <a ng-click="$ctrl.changeOrderBy('UpdatedAt')">
Last Update Last Update
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>
@ -66,7 +66,7 @@
</td> </td>
<td> <td>
<a ng-if="item.Ports && item.Ports.length > 0 && p.PublishedPort" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl }}:{{ p.PublishedPort }}" target="_blank"> <a ng-if="item.Ports && item.Ports.length > 0 && p.PublishedPort" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl }}:{{ p.PublishedPort }}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }} <i class="fa fa-external-link-alt" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
</a> </a>
<span ng-if="!item.Ports || item.Ports.length === 0">-</span> <span ng-if="!item.Ports || item.Ports.length === 0">-</span>
</td> </td>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.stacks.new"> <button type="button" class="btn btn-sm btn-primary" ui-sref="docker.stacks.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack <i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack
@ -35,15 +35,15 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th ng-if="$ctrl.showOwnershipColumn"> <th ng-if="$ctrl.showOwnershipColumn">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"> <a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership Ownership
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter"> <div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
@ -32,8 +32,8 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Name')"> <a ng-click="$ctrl.changeOrderBy('Name')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>

View file

@ -14,7 +14,7 @@
<div class="actionBar"> <div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" <button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
</div> </div>
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter"> <div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
@ -32,22 +32,22 @@
</span> </span>
<a ng-click="$ctrl.changeOrderBy('Username')"> <a ng-click="$ctrl.changeOrderBy('Username')">
Name Name
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Username' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Username' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Username' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Username' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('RoleName')"> <a ng-click="$ctrl.changeOrderBy('RoleName')">
Role Role
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
<th> <th>
<a ng-click="$ctrl.changeOrderBy('AuthenticationMethod')"> <a ng-click="$ctrl.changeOrderBy('AuthenticationMethod')">
Authentication Authentication
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'AuthenticationMethod' && !$ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'AuthenticationMethod' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'AuthenticationMethod' && $ctrl.state.reverseOrder"></i> <i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'AuthenticationMethod' && $ctrl.state.reverseOrder"></i>
</a> </a>
</th> </th>
</tr> </tr>
@ -63,7 +63,7 @@
</td> </td>
<td> <td>
<span> <span>
<i class="fa fa-user-circle-o" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role === 1 && !item.isTeamLeader"></i> <i class="fa fa-user-circle" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role === 1 && !item.isTeamLeader"></i>
<i class="fa fa-user-plus" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && item.isTeamLeader"></i> <i class="fa fa-user-plus" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && item.isTeamLeader"></i>
<i class="fa fa-user" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && !item.isTeamLeader"></i> <i class="fa fa-user" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && !item.isTeamLeader"></i>
{{ item.RoleName }} {{ item.RoleName }}

View file

@ -31,7 +31,7 @@
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca"> <input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca">
<label for="tls_client_ca"> <label for="tls_client_ca">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
TLS with server and client verification TLS with server and client verification
</div> </div>
<p>Use client certificates and server verification</p> <p>Use client certificates and server verification</p>
@ -41,7 +41,7 @@
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca"> <input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca">
<label for="tls_client_noca"> <label for="tls_client_noca">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
TLS with client verification only TLS with client verification only
</div> </div>
<p>Use client certificates without server verification</p> <p>Use client certificates without server verification</p>
@ -51,7 +51,7 @@
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca"> <input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca">
<label for="tls_ca"> <label for="tls_ca">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
TLS with server verification only TLS with server verification only
</div> </div>
<p>Only verify the server certificate</p> <p>Only verify the server certificate</p>
@ -61,7 +61,7 @@
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only"> <input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only">
<label for="tls_only"> <label for="tls_only">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
TLS only TLS only
</div> </div>
<p>No server/client verification</p> <p>No server/client verification</p>
@ -84,7 +84,7 @@
{{ $ctrl.formData.TLSCACert.name }} {{ $ctrl.formData.TLSCACert.name }}
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCACert && $ctrl.formData.TLSCACert === $ctrl.endpoint.TLSConfig.TLSCACert" aria-hidden="true"></i> <i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCACert && $ctrl.formData.TLSCACert === $ctrl.endpoint.TLSConfig.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCACert" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>
@ -100,7 +100,7 @@
{{ $ctrl.formData.TLSCert.name }} {{ $ctrl.formData.TLSCert.name }}
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCert && $ctrl.formData.TLSCert === $ctrl.endpoint.TLSConfig.TLSCert" aria-hidden="true"></i> <i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCert && $ctrl.formData.TLSCert === $ctrl.endpoint.TLSConfig.TLSCert" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCert" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCert" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>
@ -114,7 +114,7 @@
{{ $ctrl.formData.TLSKey.name }} {{ $ctrl.formData.TLSKey.name }}
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSKey && $ctrl.formData.TLSKey === $ctrl.endpoint.TLSConfig.TLSKey" aria-hidden="true"></i> <i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSKey && $ctrl.formData.TLSKey === $ctrl.endpoint.TLSConfig.TLSKey" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSKey" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSKey" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>

View file

@ -6,7 +6,7 @@ angular.module('portainer.app')
link: function (scope, iElement, iAttrs) { link: function (scope, iElement, iAttrs) {
scope.username = Authentication.getUserDetails().username; scope.username = Authentication.getUserDetails().username;
}, },
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="portainer.account" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> my account </u></a><a ui-sref="portainer.auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out" aria-hidden="true"></i> log out</u></a></div></div>', template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="portainer.account" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> my account </u></a><a ui-sref="portainer.auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out-alt" aria-hidden="true"></i> log out</u></a></div></div>',
restrict: 'E' restrict: 'E'
}; };
return directive; return directive;

View file

@ -10,7 +10,7 @@ angular.module('portainer.app')
scope.displayDonationHeader = StateManager.getState().application.displayDonationHeader; scope.displayDonationHeader = StateManager.getState().application.displayDonationHeader;
}, },
transclude: true, transclude: true,
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span><a ng-if="displayDonationHeader" ui-sref="portainer.about" class="pull-right" style="font-size:14px;margin-right:15px;margin-top:2px;"><span class="fa fa-heart fa-fw red-icon"></span> Help support portainer</a></div>', 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" aria-hidden="true"></i> {{username}}</span><a ng-if="displayDonationHeader" ui-sref="portainer.about" class="pull-right" style="font-size:14px;margin-right:15px;margin-top:2px;"><span class="fa fa-heart fa-fw red-icon"></span> Help support portainer</a></div>',
restrict: 'E' restrict: 'E'
}; };
return directive; return directive;

View file

@ -37,8 +37,6 @@ angular.module('portainer.app')
service.encodedCredentials = function(registry) { service.encodedCredentials = function(registry) {
var credentials = { var credentials = {
username: registry.Username,
password: registry.Password,
serveraddress: registry.URL serveraddress: registry.URL
}; };
return btoa(JSON.stringify(credentials)); return btoa(JSON.stringify(credentials));

View file

@ -12,7 +12,7 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
var endpointAPIVersion = parseFloat(data.ApiVersion); var endpointAPIVersion = parseFloat(data.ApiVersion);
return $q.all([ return $q.all([
endpointAPIVersion >= 1.25 ? initStoridgeExtension(): null endpointAPIVersion >= 1.25 ? initStoridgeExtension(): {}
]); ]);
}) })
.then(function success(data) { .then(function success(data) {

View file

@ -28,23 +28,23 @@
<p> <p>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Fund our work</u> <i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Fund our work</u>
<ul> <ul>
<li>Become a <a href="https://www.patreon.com/Portainerio" target="_blank"><i class="fa fa-money" aria-hidden="true"></i> patron</a></li> <li>Become a <a href="https://www.patreon.com/Portainerio" target="_blank"><i class="fa fa-money-bill-alt" aria-hidden="true"></i> patron</a></li>
<li>Consider a <a href="https://portainer.io/support.html" target="_blank">paid support plan</a></li> <li>Consider a <a href="https://portainer.io/support.html" target="_blank">paid support plan</a></li>
<li>Make a <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6" target="_blank"><i class="fa fa-paypal" aria-hidden="true"></i> donation</a></li> <li>Make a <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6" target="_blank"><i class="fab fa-paypal" aria-hidden="true"></i> donation</a></li>
</ul> </ul>
</p> </p>
<p> <p>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Contribute</u> <i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Contribute</u>
<ul> <ul>
<li>Found a bug or got a feature idea? Let's talk about it on <a href="https://github.com/portainer/portainer/issues" target="_blank"><i class="fa fa-github" aria-hidden="true"></i> Github</a></li> <li>Found a bug or got a feature idea? Let's talk about it on <a href="https://github.com/portainer/portainer/issues" target="_blank"><i class="fab fa-github" aria-hidden="true"></i> Github</a></li>
<li>Follow our <a href="https://portainer.readthedocs.io/en/latest/contribute.html" target="_blank">contribution guidelines</a> to build it locally and make a <a target="_blank" href="https://github.com/portainer/portainer/compare"><i class="fa fa-github" aria-hidden="true"></i> pull request</a></li> <li>Follow our <a href="https://portainer.readthedocs.io/en/latest/contribute.html" target="_blank">contribution guidelines</a> to build it locally and make a <a target="_blank" href="https://github.com/portainer/portainer/compare"><i class="fab fa-github" aria-hidden="true"></i> pull request</a></li>
</ul> </ul>
</p> </p>
<p> <p>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Spread the word</u> <i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Spread the word</u>
<ul> <ul>
<li>Talk to your friends and colleagues about how awesome Portainer is!</li> <li>Talk to your friends and colleagues about how awesome Portainer is!</li>
<li>Follow us on <a href="https://twitter.com/portainerio" target="_blank"><i class="fa fa-twitter" aria-hidden="true"></i> Twitter</a></li> <li>Follow us on <a href="https://twitter.com/portainerio" target="_blank"><i class="fab fa-twitter" aria-hidden="true"></i> Twitter</a></li>
</ul> </ul>
</p> </p>
</div> </div>
@ -56,7 +56,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<rd-widget> <rd-widget>
<rd-widget-header title="Support and services" icon="fa-building-o"></rd-widget-header> <rd-widget-header title="Support and services" icon="fa-building"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<div class="small" style="line-height:1.65;"> <div class="small" style="line-height:1.65;">
<p> <p>
@ -69,15 +69,15 @@
<p> <p>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Community support</u> <i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Community support</u>
<ul> <ul>
<li>Join us on <a href="https://portainer.io/slack/" target="_blank"><i class="fa fa-slack" aria-hidden="true"></i> Slack</a></li> <li>Join us on <a href="https://portainer.io/slack/" target="_blank"><i class="fab fa-slack" aria-hidden="true"></i> Slack</a></li>
<li>We're also on <a href="https://gitter.im/portainer/Lobby" target="_blank"><i class="fa fa-github-alt" aria-hidden="true"></i> Gitter</a></li> <li>We're also on <a href="https://gitter.im/portainer/Lobby" target="_blank"><i class="fab fa-github-alt" aria-hidden="true"></i> Gitter</a></li>
</ul> </ul>
</p> </p>
<p> <p>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Services</u> <i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Services</u>
<ul> <ul>
<li>Find out more about our <a href="https://portainer.io/support.html" target="_blank">consulting and commercial support plans</a></li> <li>Find out more about our <a href="https://portainer.io/support.html" target="_blank">consulting and commercial support plans</a></li>
<li>We also propose a fund-a-feature plan, reach out to us at <a target="_blank" href="mailto:info@portainer.io"><i class="fa fa-envelope-o" aria-hidden="true"></i> portainer.io</a></li> <li>We also propose a fund-a-feature plan, reach out to us at <a target="_blank" href="mailto:info@portainer.io"><i class="fa fa-envelope" aria-hidden="true"></i> portainer.io</a></li>
</ul> </ul>
</p> </p>
</div> </div>

View file

@ -28,7 +28,7 @@
<!-- login button --> <!-- login button -->
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in" aria-hidden="true"></i> Login</button> <button type="submit" class="btn btn-primary btn-sm pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in-alt" aria-hidden="true"></i> Login</button>
<span class="pull-left" style="margin: 5px;" ng-if="state.AuthenticationError"> <span class="pull-left" style="margin: 5px;" ng-if="state.AuthenticationError">
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<span class="small text-danger">{{ state.AuthenticationError }}</span> <span class="small text-danger">{{ state.AuthenticationError }}</span>

View file

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Endpoints"> <rd-header-title title="Endpoints">
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.endpoints" ui-sref-opts="{reload: true}"> <a data-toggle="tooltip" title="Refresh" ui-sref="portainer.endpoints" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i> <i class="fa fa-sync" aria-hidden="true"></i>
</a> </a>
</rd-header-title> </rd-header-title>
<rd-header-content>Endpoint management</rd-header-content> <rd-header-content>Endpoint management</rd-header-content>

View file

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.controller('InitAdminController', ['$scope', '$state', '$sanitize', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'EndpointProvider', .controller('InitAdminController', ['$scope', '$state', '$sanitize', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'EndpointProvider', 'ExtensionManager',
function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService, EndpointService, EndpointProvider) { function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService, EndpointService, EndpointProvider, ExtensionManager) {
$scope.logo = StateManager.getState().application.logo; $scope.logo = StateManager.getState().application.logo;
@ -31,8 +31,13 @@ function ($scope, $state, $sanitize, Notifications, Authentication, StateManager
$state.go('portainer.init.endpoint'); $state.go('portainer.init.endpoint');
} else { } else {
var endpoint = data[0]; var endpoint = data[0];
EndpointProvider.setEndpointID(endpoint.Id); endpointID = endpoint.Id;
StateManager.updateEndpointState(false, endpoint.Extensions) EndpointProvider.setEndpointID(endpointID);
ExtensionManager.initEndpointExtensions(endpointID)
.then(function success(data) {
var extensions = data;
return StateManager.updateEndpointState(false, extensions);
})
.then(function success() { .then(function success() {
$state.go('docker.dashboard'); $state.go('docker.dashboard');
}) })

View file

@ -148,7 +148,7 @@
<span style="margin-left: 5px;"> <span style="margin-left: 5px;">
{{ formValues.TLSCACert.name }} {{ formValues.TLSCACert.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>
@ -162,7 +162,7 @@
<span style="margin-left: 5px;"> <span style="margin-left: 5px;">
{{ formValues.TLSCert.name }} {{ formValues.TLSCert.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>
@ -175,7 +175,7 @@
<span style="margin-left: 5px;"> <span style="margin-left: 5px;">
{{ formValues.TLSKey.name }} {{ formValues.TLSKey.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i> <i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i>
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i> <i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span> </span>
</div> </div>
</div> </div>

Some files were not shown because too many files have changed in this diff Show more