feat(azure): add experimental Azure endpoint support (#1936)
parent
415c6ce5e1
commit
9ad9cc5e2d
|
@ -44,6 +44,11 @@ const (
|
|||
ErrEndpointAccessDenied = Error("Access denied to endpoint")
|
||||
)
|
||||
|
||||
// Azure environment errors
|
||||
const (
|
||||
ErrAzureInvalidCredentials = Error("Invalid Azure credentials")
|
||||
)
|
||||
|
||||
// Endpoint group errors.
|
||||
const (
|
||||
ErrEndpointGroupNotFound = Error("Endpoint group not found")
|
||||
|
|
|
@ -2,15 +2,68 @@ package client
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
// HTTPClient represents a client to send HTTP requests.
|
||||
type HTTPClient struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
// NewHTTPClient is used to build a new HTTPClient.
|
||||
func NewHTTPClient() *HTTPClient {
|
||||
return &HTTPClient{
|
||||
&http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AzureAuthenticationResponse represents an Azure API authentication response.
|
||||
type AzureAuthenticationResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
}
|
||||
|
||||
// ExecuteAzureAuthenticationRequest is used to execute an authentication request
|
||||
// against the Azure API. It re-uses the same http.Client.
|
||||
func (client *HTTPClient) ExecuteAzureAuthenticationRequest(credentials *portainer.AzureCredentials) (*AzureAuthenticationResponse, error) {
|
||||
loginURL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", credentials.TenantID)
|
||||
params := url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {credentials.ApplicationID},
|
||||
"client_secret": {credentials.AuthenticationKey},
|
||||
"resource": {"https://management.azure.com/"},
|
||||
}
|
||||
|
||||
response, err := client.PostForm(loginURL, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, portainer.ErrAzureInvalidCredentials
|
||||
}
|
||||
|
||||
var token AzureAuthenticationResponse
|
||||
err = json.NewDecoder(response.Body).Decode(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment
|
||||
// using the specified host and optional TLS configuration.
|
||||
// It uses a new Http.Client for each operation.
|
||||
func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) {
|
||||
transport := &http.Transport{}
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// AzureHandler represents an HTTP API handler for proxying requests to the Azure API.
|
||||
type AzureHandler struct {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewAzureHandler returns a new instance of AzureHandler.
|
||||
func NewAzureHandler(bouncer *security.RequestBouncer) *AzureHandler {
|
||||
h := &AzureHandler{
|
||||
Router: mux.NewRouter(),
|
||||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
}
|
||||
h.PathPrefix("/{id}/azure").Handler(
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.proxyRequestsToAzureAPI)))
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *AzureHandler) checkEndpointAccess(endpoint *portainer.Endpoint, userID portainer.UserID) error {
|
||||
memberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group, err := handler.EndpointGroupService.EndpointGroup(endpoint.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !security.AuthorizedEndpointAccess(endpoint, group, userID, memberships) {
|
||||
return portainer.ErrEndpointAccessDenied
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *AzureHandler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
parsedID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
endpointID := portainer.EndpointID(parsedID)
|
||||
endpoint, err := handler.EndpointService.Endpoint(endpointID)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if tokenData.Role != portainer.AdministratorRole {
|
||||
err = handler.checkEndpointAccess(endpoint, tokenData.ID)
|
||||
if err != nil && err == portainer.ErrEndpointAccessDenied {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusForbidden, handler.Logger)
|
||||
return
|
||||
} else if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var proxy http.Handler
|
||||
proxy = handler.ProxyManager.GetProxy(string(endpointID))
|
||||
if proxy == nil {
|
||||
proxy, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.StripPrefix("/"+id+"/azure", proxy).ServeHTTP(w, r)
|
||||
}
|
|
@ -80,6 +80,7 @@ type (
|
|||
postEndpointPayload struct {
|
||||
name string
|
||||
url string
|
||||
endpointType int
|
||||
publicURL string
|
||||
groupID int
|
||||
useTLS bool
|
||||
|
@ -88,6 +89,9 @@ type (
|
|||
caCert []byte
|
||||
cert []byte
|
||||
key []byte
|
||||
azureApplicationID string
|
||||
azureTenantID string
|
||||
azureAuthenticationKey string
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -117,9 +121,46 @@ func (handler *EndpointHandler) handleGetEndpoints(w http.ResponseWriter, r *htt
|
|||
return
|
||||
}
|
||||
|
||||
for i := range filteredEndpoints {
|
||||
filteredEndpoints[i].AzureCredentials = portainer.AzureCredentials{}
|
||||
}
|
||||
|
||||
encodeJSON(w, filteredEndpoints, handler.Logger)
|
||||
}
|
||||
|
||||
func (handler *EndpointHandler) createAzureEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
credentials := portainer.AzureCredentials{
|
||||
ApplicationID: payload.azureApplicationID,
|
||||
TenantID: payload.azureTenantID,
|
||||
AuthenticationKey: payload.azureAuthenticationKey,
|
||||
}
|
||||
|
||||
httpClient := client.NewHTTPClient()
|
||||
_, err := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint := &portainer.Endpoint{
|
||||
Name: payload.name,
|
||||
URL: payload.url,
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.groupID),
|
||||
PublicURL: payload.publicURL,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
AzureCredentials: credentials,
|
||||
}
|
||||
|
||||
err = handler.EndpointService.CreateEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
func (handler *EndpointHandler) createTLSSecuredEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromBytes(payload.caCert, payload.cert, payload.key, payload.skipTLSClientVerification, payload.skipTLSServerVerification)
|
||||
if err != nil {
|
||||
|
@ -236,6 +277,10 @@ func (handler *EndpointHandler) createUnsecuredEndpoint(payload *postEndpointPay
|
|||
}
|
||||
|
||||
func (handler *EndpointHandler) createEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
if portainer.EndpointType(payload.endpointType) == portainer.AzureEnvironment {
|
||||
return handler.createAzureEndpoint(payload)
|
||||
}
|
||||
|
||||
if payload.useTLS {
|
||||
return handler.createTLSSecuredEndpoint(payload)
|
||||
}
|
||||
|
@ -245,11 +290,35 @@ func (handler *EndpointHandler) createEndpoint(payload *postEndpointPayload) (*p
|
|||
func convertPostEndpointRequestToPayload(r *http.Request) (*postEndpointPayload, error) {
|
||||
payload := &postEndpointPayload{}
|
||||
payload.name = r.FormValue("Name")
|
||||
|
||||
endpointType := r.FormValue("EndpointType")
|
||||
|
||||
if payload.name == "" || endpointType == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
|
||||
parsedType, err := strconv.Atoi(endpointType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload.url = r.FormValue("URL")
|
||||
payload.endpointType = parsedType
|
||||
|
||||
if portainer.EndpointType(payload.endpointType) != portainer.AzureEnvironment && payload.url == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
|
||||
payload.publicURL = r.FormValue("PublicURL")
|
||||
|
||||
if payload.name == "" || payload.url == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
if portainer.EndpointType(payload.endpointType) == portainer.AzureEnvironment {
|
||||
payload.azureApplicationID = r.FormValue("AzureApplicationID")
|
||||
payload.azureTenantID = r.FormValue("AzureTenantID")
|
||||
payload.azureAuthenticationKey = r.FormValue("AzureAuthenticationKey")
|
||||
|
||||
if payload.azureApplicationID == "" || payload.azureTenantID == "" || payload.azureAuthenticationKey == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
}
|
||||
|
||||
rawGroupID := r.FormValue("GroupID")
|
||||
|
@ -336,6 +405,8 @@ func (handler *EndpointHandler) handleGetEndpoint(w http.ResponseWriter, r *http
|
|||
return
|
||||
}
|
||||
|
||||
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
||||
|
||||
encodeJSON(w, endpoint, handler.Logger)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ type Handler struct {
|
|||
SettingsHandler *SettingsHandler
|
||||
TemplatesHandler *TemplatesHandler
|
||||
DockerHandler *DockerHandler
|
||||
AzureHandler *AzureHandler
|
||||
WebSocketHandler *WebSocketHandler
|
||||
UploadHandler *UploadHandler
|
||||
FileHandler *FileHandler
|
||||
|
@ -64,6 +65,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api/endpoints", h.StoridgeHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/extensions"):
|
||||
http.StripPrefix("/api/endpoints", h.ExtensionHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/azure/"):
|
||||
http.StripPrefix("/api/endpoints", h.AzureHandler).ServeHTTP(w, r)
|
||||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
)
|
||||
|
||||
type (
|
||||
azureAPIToken struct {
|
||||
value string
|
||||
expirationTime time.Time
|
||||
}
|
||||
|
||||
// AzureTransport represents a transport used when executing HTTP requests
|
||||
// against the Azure API.
|
||||
AzureTransport struct {
|
||||
credentials *portainer.AzureCredentials
|
||||
client *client.HTTPClient
|
||||
token *azureAPIToken
|
||||
mutex sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
// NewAzureTransport returns a pointer to an AzureTransport instance.
|
||||
func NewAzureTransport(credentials *portainer.AzureCredentials) *AzureTransport {
|
||||
return &AzureTransport{
|
||||
credentials: credentials,
|
||||
client: client.NewHTTPClient(),
|
||||
}
|
||||
}
|
||||
|
||||
func (transport *AzureTransport) authenticate() error {
|
||||
token, err := transport.client.ExecuteAzureAuthenticationRequest(transport.credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expiresOn, err := strconv.ParseInt(token.ExpiresOn, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transport.token = &azureAPIToken{
|
||||
value: token.AccessToken,
|
||||
expirationTime: time.Unix(expiresOn, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transport *AzureTransport) retrieveAuthenticationToken() error {
|
||||
transport.mutex.Lock()
|
||||
defer transport.mutex.Unlock()
|
||||
|
||||
if transport.token == nil {
|
||||
return transport.authenticate()
|
||||
}
|
||||
|
||||
timeLimit := time.Now().Add(-5 * time.Minute)
|
||||
if timeLimit.After(transport.token.expirationTime) {
|
||||
return transport.authenticate()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoundTrip is the implementation of the Transport interface.
|
||||
func (transport *AzureTransport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
err := transport.retrieveAuthenticationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Authorization", "Bearer "+transport.token.value)
|
||||
return http.DefaultTransport.RoundTrip(request)
|
||||
}
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/portainer/portainer/crypto"
|
||||
)
|
||||
|
||||
const azureAPIBaseURL = "https://management.azure.com"
|
||||
|
||||
// proxyFactory is a factory to create reverse proxies to Docker endpoints
|
||||
type proxyFactory struct {
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
|
@ -20,11 +22,23 @@ type proxyFactory struct {
|
|||
SignatureService portainer.DigitalSignatureService
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler {
|
||||
func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
|
||||
u.Scheme = "http"
|
||||
return newSingleHostReverseProxyWithHostHeader(u)
|
||||
}
|
||||
|
||||
func newAzureProxy(credentials *portainer.AzureCredentials) (http.Handler, error) {
|
||||
url, err := url.Parse(azureAPIBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := newSingleHostReverseProxyWithHostHeader(url)
|
||||
proxy.Transport = NewAzureTransport(credentials)
|
||||
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portainer.TLSConfiguration, enableSignature bool) (http.Handler, error) {
|
||||
u.Scheme = "https"
|
||||
|
||||
|
|
|
@ -44,33 +44,39 @@ func NewManager(parameters *ManagerParams) *Manager {
|
|||
}
|
||||
}
|
||||
|
||||
// CreateAndRegisterProxy creates a new HTTP reverse proxy and adds it to the registered proxies.
|
||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||
func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
var proxy http.Handler
|
||||
func (manager *Manager) createDockerProxy(endpointURL *url.URL, tlsConfig *portainer.TLSConfiguration) (http.Handler, error) {
|
||||
if endpointURL.Scheme == "tcp" {
|
||||
if tlsConfig.TLS || tlsConfig.TLSSkipVerify {
|
||||
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, tlsConfig, false)
|
||||
}
|
||||
return manager.proxyFactory.newDockerHTTPProxy(endpointURL, false), nil
|
||||
}
|
||||
// Assume unix:// scheme
|
||||
return manager.proxyFactory.newDockerSocketProxy(endpointURL.Path), nil
|
||||
}
|
||||
|
||||
func (manager *Manager) createProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
endpointURL, err := url.Parse(endpoint.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enableSignature := false
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
enableSignature = true
|
||||
switch endpoint.Type {
|
||||
case portainer.AgentOnDockerEnvironment:
|
||||
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, true)
|
||||
case portainer.AzureEnvironment:
|
||||
return newAzureProxy(&endpoint.AzureCredentials)
|
||||
default:
|
||||
return manager.createDockerProxy(endpointURL, &endpoint.TLSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if endpointURL.Scheme == "tcp" {
|
||||
if endpoint.TLSConfig.TLS {
|
||||
proxy, err = manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, enableSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
proxy = manager.proxyFactory.newDockerHTTPProxy(endpointURL, enableSignature)
|
||||
}
|
||||
} else {
|
||||
// Assume unix:// scheme
|
||||
proxy = manager.proxyFactory.newDockerSocketProxy(endpointURL.Path)
|
||||
// CreateAndRegisterProxy creates a new HTTP reverse proxy based on endpoint properties and and adds it to the registered proxies.
|
||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||
func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
proxy, err := manager.createProxy(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manager.proxies.Set(string(endpoint.ID), proxy)
|
||||
|
@ -99,7 +105,7 @@ func (manager *Manager) CreateAndRegisterExtensionProxy(key, extensionAPIURL str
|
|||
return nil, err
|
||||
}
|
||||
|
||||
proxy := manager.proxyFactory.newExtensionHTTPPRoxy(extensionURL)
|
||||
proxy := manager.proxyFactory.newHTTPProxy(extensionURL)
|
||||
manager.extensionProxies.Set(key, proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// NewSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy
|
||||
// newSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy
|
||||
// from golang.org/src/net/http/httputil/reverseproxy.go and merely sets the Host
|
||||
// HTTP header, which NewSingleHostReverseProxy deliberately preserves.
|
||||
func newSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseProxy {
|
||||
|
|
|
@ -88,6 +88,11 @@ func (server *Server) Start() error {
|
|||
dockerHandler.EndpointGroupService = server.EndpointGroupService
|
||||
dockerHandler.TeamMembershipService = server.TeamMembershipService
|
||||
dockerHandler.ProxyManager = proxyManager
|
||||
var azureHandler = handler.NewAzureHandler(requestBouncer)
|
||||
azureHandler.EndpointService = server.EndpointService
|
||||
azureHandler.EndpointGroupService = server.EndpointGroupService
|
||||
azureHandler.TeamMembershipService = server.TeamMembershipService
|
||||
azureHandler.ProxyManager = proxyManager
|
||||
var websocketHandler = handler.NewWebSocketHandler()
|
||||
websocketHandler.EndpointService = server.EndpointService
|
||||
websocketHandler.SignatureService = server.SignatureService
|
||||
|
@ -140,6 +145,7 @@ func (server *Server) Start() error {
|
|||
StackHandler: stackHandler,
|
||||
TemplatesHandler: templatesHandler,
|
||||
DockerHandler: dockerHandler,
|
||||
AzureHandler: azureHandler,
|
||||
WebSocketHandler: websocketHandler,
|
||||
FileHandler: fileHandler,
|
||||
UploadHandler: uploadHandler,
|
||||
|
|
|
@ -175,16 +175,17 @@ type (
|
|||
// Endpoint represents a Docker endpoint with all the info required
|
||||
// to connect to it.
|
||||
Endpoint struct {
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type EndpointType `json:"Type"`
|
||||
URL string `json:"URL"`
|
||||
GroupID EndpointGroupID `json:"GroupId"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type EndpointType `json:"Type"`
|
||||
URL string `json:"URL"`
|
||||
GroupID EndpointGroupID `json:"GroupId"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 4
|
||||
|
@ -194,6 +195,14 @@ type (
|
|||
TLSKeyPath string `json:"TLSKey,omitempty"`
|
||||
}
|
||||
|
||||
// AzureCredentials represents the credentials used to connect to an Azure
|
||||
// environment.
|
||||
AzureCredentials struct {
|
||||
ApplicationID string `json:"ApplicationID"`
|
||||
TenantID string `json:"TenantID"`
|
||||
AuthenticationKey string `json:"AuthenticationKey"`
|
||||
}
|
||||
|
||||
// EndpointGroupID represents an endpoint group identifier.
|
||||
EndpointGroupID int
|
||||
|
||||
|
@ -530,4 +539,6 @@ const (
|
|||
DockerEnvironment
|
||||
// AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment
|
||||
AgentOnDockerEnvironment
|
||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
||||
AzureEnvironment
|
||||
)
|
||||
|
|
|
@ -18,6 +18,7 @@ angular.module('portainer', [
|
|||
'portainer.templates',
|
||||
'portainer.app',
|
||||
'portainer.agent',
|
||||
'portainer.azure',
|
||||
'portainer.docker',
|
||||
'extension.storidge',
|
||||
'rzModule']);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
angular.module('portainer.azure', ['portainer.app'])
|
||||
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
|
||||
var azure = {
|
||||
name: 'azure',
|
||||
parent: 'root',
|
||||
abstract: true
|
||||
};
|
||||
|
||||
var containerInstances = {
|
||||
name: 'azure.containerinstances',
|
||||
url: '/containerinstances',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/azure/views/containerinstances/containerinstances.html',
|
||||
controller: 'AzureContainerInstancesController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containerInstanceCreation = {
|
||||
name: 'azure.containerinstances.new',
|
||||
url: '/new/',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/azure/views/containerinstances/create/createcontainerinstance.html',
|
||||
controller: 'AzureCreateContainerInstanceController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dashboard = {
|
||||
name: 'azure.dashboard',
|
||||
url: '/dashboard',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/azure/views/dashboard/dashboard.html',
|
||||
controller: 'AzureDashboardController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(azure);
|
||||
$stateRegistryProvider.register(containerInstances);
|
||||
$stateRegistryProvider.register(containerInstanceCreation);
|
||||
$stateRegistryProvider.register(dashboard);
|
||||
}]);
|
|
@ -0,0 +1,3 @@
|
|||
angular.module('portainer.azure').component('azureSidebarContent', {
|
||||
templateUrl: 'app/azure/components/azure-sidebar-content/azureSidebarContent.html'
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
<li class="sidebar-list">
|
||||
<a ui-sref="azure.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="azure.containerinstances" ui-sref-active="active">Container instances <span class="menu-icon fa fa-server fa-fw"></span></a>
|
||||
</li>
|
|
@ -0,0 +1,104 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="azure.containerinstances.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add container
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<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-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Location')">
|
||||
Location
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Location' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Location' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
Published Ports
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="azure.containerinstances.container({ id: item.Id })">{{ item.Name | truncate:50 }}</a>
|
||||
</td>
|
||||
<td>{{ item.Location }}</td>
|
||||
<td>
|
||||
<a ng-if="item.Ports.length > 0" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ item.IPAddress }}:{{ p.port }}" target="_blank">
|
||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i> :{{ p.port }}
|
||||
</a>
|
||||
<span ng-if="item.Ports.length == 0" >-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No container available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
angular.module('portainer.azure').component('containergroupsDatatable', {
|
||||
templateUrl: 'app/azure/components/datatables/containergroups-datatable/containerGroupsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
function ContainerGroupDefaultModel() {
|
||||
this.Location = '';
|
||||
this.OSType = 'Linux';
|
||||
this.Name = '';
|
||||
this.Image = '';
|
||||
this.AllocatePublicIP = true;
|
||||
this.Ports = [
|
||||
{
|
||||
container: 80,
|
||||
host: 80,
|
||||
protocol: 'TCP'
|
||||
}
|
||||
];
|
||||
this.CPU = 1;
|
||||
this.Memory = 1;
|
||||
}
|
||||
|
||||
function ContainerGroupViewModel(data, subscriptionId, resourceGroupName) {
|
||||
this.Id = data.id;
|
||||
this.Name = data.name;
|
||||
this.Location = data.location;
|
||||
this.IPAddress = data.properties.ipAddress.ip;
|
||||
this.Ports = data.properties.ipAddress.ports;
|
||||
}
|
||||
|
||||
function CreateContainerGroupRequest(model) {
|
||||
this.location = model.Location;
|
||||
|
||||
var containerPorts = [];
|
||||
var addressPorts = [];
|
||||
for (var i = 0; i < model.Ports.length; i++) {
|
||||
var binding = model.Ports[i];
|
||||
|
||||
containerPorts.push({
|
||||
port: binding.container
|
||||
});
|
||||
|
||||
addressPorts.push({
|
||||
port: binding.host,
|
||||
protocol: binding.protocol
|
||||
});
|
||||
}
|
||||
|
||||
this.properties = {
|
||||
osType: model.OSType,
|
||||
containers: [
|
||||
{
|
||||
name: model.Name,
|
||||
properties: {
|
||||
image: model.Image,
|
||||
ports: containerPorts,
|
||||
resources: {
|
||||
requests: {
|
||||
cpu: model.CPU,
|
||||
memoryInGB: model.Memory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
ipAddress: {
|
||||
type: model.AllocatePublicIP ? 'Public': 'Private',
|
||||
ports: addressPorts
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
function LocationViewModel(data) {
|
||||
this.Id = data.id;
|
||||
this.SubscriptionId = data.subscriptionId;
|
||||
this.DisplayName = data.displayName;
|
||||
this.Name = data.name;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
function ContainerInstanceProviderViewModel(data) {
|
||||
this.Id = data.id;
|
||||
this.Namespace = data.namespace;
|
||||
|
||||
var containerGroupType = _.find(data.resourceTypes, { 'resourceType': 'containerGroups' });
|
||||
this.Locations = containerGroupType.locations;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
function ResourceGroupViewModel(data, subscriptionId) {
|
||||
this.Id = data.id;
|
||||
this.SubscriptionId = subscriptionId;
|
||||
this.Name = data.name;
|
||||
this.Location = data.location;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
function SubscriptionViewModel(data) {
|
||||
this.Id = data.subscriptionId;
|
||||
this.Name = data.displayName;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('Azure', ['$http', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function AzureFactory($http, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.delete = function(id, apiVersion) {
|
||||
var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/azure' + id + '?api-version=' + apiVersion;
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: url
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,41 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('ContainerGroup', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function ContainerGroupFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
|
||||
var resource = {};
|
||||
|
||||
var base = $resource(
|
||||
API_ENDPOINT_ENDPOINTS +'/:endpointId/azure/subscriptions/:subscriptionId/providers/Microsoft.ContainerInstance/containerGroups',
|
||||
{
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2018-04-01'
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } }
|
||||
}
|
||||
);
|
||||
|
||||
var withResourceGroup = $resource(
|
||||
API_ENDPOINT_ENDPOINTS +'/:endpointId/azure/subscriptions/:subscriptionId/resourceGroups/:resourceGroupName/providers/Microsoft.ContainerInstance/containerGroups/:containerGroupName',
|
||||
{
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2018-04-01'
|
||||
},
|
||||
{
|
||||
create: {
|
||||
method: 'PUT',
|
||||
params: {
|
||||
subscriptionId: '@subscriptionId',
|
||||
resourceGroupName: '@resourceGroupName',
|
||||
containerGroupName: '@containerGroupName'
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
resource.query = base.query;
|
||||
resource.create = withResourceGroup.create;
|
||||
|
||||
return resource;
|
||||
}]);
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('Location', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function LocationFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS +'/:endpointId/azure/subscriptions/:subscriptionId/locations', {
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2016-06-01'
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } }
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('Provider', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function ProviderFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS +'/:endpointId/azure/subscriptions/:subscriptionId/providers/:providerNamespace', {
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2018-02-01'
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: { subscriptionId: '@subscriptionId', providerNamespace: '@providerNamespace' } }
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('ResourceGroup', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function ResourceGroupFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS +'/:endpointId/azure/subscriptions/:subscriptionId/resourcegroups', {
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2018-02-01'
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } }
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('Subscription', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
|
||||
function SubscriptionFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions', {
|
||||
'endpointId': EndpointProvider.endpointID,
|
||||
'api-version': '2016-06-01'
|
||||
},
|
||||
{
|
||||
query: { method: 'GET' }
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,66 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('AzureService', ['$q', 'Azure', 'SubscriptionService', 'ResourceGroupService', 'ContainerGroupService', 'ProviderService',
|
||||
function AzureServiceFactory($q, Azure, SubscriptionService, ResourceGroupService, ContainerGroupService, ProviderService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.deleteContainerGroup = function(id) {
|
||||
return Azure.delete(id, '2018-04-01');
|
||||
};
|
||||
|
||||
service.createContainerGroup = function(model, subscriptionId, resourceGroupName) {
|
||||
return ContainerGroupService.create(model, subscriptionId, resourceGroupName);
|
||||
};
|
||||
|
||||
service.subscriptions = function() {
|
||||
return SubscriptionService.subscriptions();
|
||||
};
|
||||
|
||||
service.containerInstanceProvider = function(subscriptions) {
|
||||
return retrieveResourcesForEachSubscription(subscriptions, ProviderService.containerInstanceProvider);
|
||||
};
|
||||
|
||||
service.resourceGroups = function(subscriptions) {
|
||||
return retrieveResourcesForEachSubscription(subscriptions, ResourceGroupService.resourceGroups);
|
||||
};
|
||||
|
||||
service.containerGroups = function(subscriptions) {
|
||||
return retrieveResourcesForEachSubscription(subscriptions, ContainerGroupService.containerGroups);
|
||||
};
|
||||
|
||||
service.aggregate = function(resourcesBySubcription) {
|
||||
var aggregatedResources = [];
|
||||
Object.keys(resourcesBySubcription).forEach(function(key, index) {
|
||||
aggregatedResources = aggregatedResources.concat(resourcesBySubcription[key]);
|
||||
});
|
||||
return aggregatedResources;
|
||||
};
|
||||
|
||||
function retrieveResourcesForEachSubscription(subscriptions, resourceQuery) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var resources = {};
|
||||
|
||||
var resourceQueries = [];
|
||||
for (var i = 0; i < subscriptions.length; i++) {
|
||||
var subscription = subscriptions[i];
|
||||
resourceQueries.push(resourceQuery(subscription.Id));
|
||||
}
|
||||
|
||||
$q.all(resourceQueries)
|
||||
.then(function success(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var result = data[i];
|
||||
resources[subscriptions[i].Id] = result;
|
||||
}
|
||||
deferred.resolve(resources);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve resources', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,33 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('ContainerGroupService', ['$q', 'ContainerGroup', function ContainerGroupServiceFactory($q, ContainerGroup) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.containerGroups = function(subscriptionId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
ContainerGroup.query({ subscriptionId: subscriptionId }).$promise
|
||||
.then(function success(data) {
|
||||
var containerGroups = data.value.map(function (item) {
|
||||
return new ContainerGroupViewModel(item);
|
||||
});
|
||||
deferred.resolve(containerGroups);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve container groups', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.create = function(model, subscriptionId, resourceGroupName) {
|
||||
var payload = new CreateContainerGroupRequest(model);
|
||||
return ContainerGroup.create({
|
||||
subscriptionId: subscriptionId,
|
||||
resourceGroupName: resourceGroupName,
|
||||
containerGroupName: model.Name
|
||||
}, payload).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('LocationService', ['$q', 'Location', function LocationServiceFactory($q, Location) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.locations = function(subscriptionId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Location.query({ subscriptionId: subscriptionId }).$promise
|
||||
.then(function success(data) {
|
||||
var locations = data.value.map(function (item) {
|
||||
return new LocationViewModel(item);
|
||||
});
|
||||
deferred.resolve(locations);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve locations', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,22 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('ProviderService', ['$q', 'Provider', function ProviderServiceFactory($q, Provider) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.containerInstanceProvider = function(subscriptionId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Provider.get({ subscriptionId: subscriptionId, providerNamespace: 'Microsoft.ContainerInstance' }).$promise
|
||||
.then(function success(data) {
|
||||
var provider = new ContainerInstanceProviderViewModel(data);
|
||||
deferred.resolve(provider);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve provider', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('ResourceGroupService', ['$q', 'ResourceGroup', function ResourceGroupServiceFactory($q, ResourceGroup) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.resourceGroups = function(subscriptionId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
ResourceGroup.query({ subscriptionId: subscriptionId }).$promise
|
||||
.then(function success(data) {
|
||||
var resourceGroups = data.value.map(function (item) {
|
||||
return new ResourceGroupViewModel(item, subscriptionId);
|
||||
});
|
||||
deferred.resolve(resourceGroups);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve resource groups', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.azure')
|
||||
.factory('SubscriptionService', ['$q', 'Subscription', function SubscriptionServiceFactory($q, Subscription) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.subscriptions = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Subscription.query({}).$promise
|
||||
.then(function success(data) {
|
||||
var subscriptions = data.value.map(function (item) {
|
||||
return new SubscriptionViewModel(item);
|
||||
});
|
||||
deferred.resolve(subscriptions);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve subscriptions', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,41 @@
|
|||
angular.module('portainer.azure')
|
||||
.controller('AzureContainerInstancesController', ['$scope', '$state', 'AzureService', 'Notifications',
|
||||
function ($scope, $state, AzureService, Notifications) {
|
||||
|
||||
function initView() {
|
||||
AzureService.subscriptions()
|
||||
.then(function success(data) {
|
||||
var subscriptions = data;
|
||||
return AzureService.containerGroups(subscriptions);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.containerGroups = AzureService.aggregate(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to load container groups');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.deleteAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (item) {
|
||||
AzureService.deleteContainerGroup(item.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Container group successfully removed', item.Name);
|
||||
var index = $scope.containerGroups.indexOf(item);
|
||||
$scope.containerGroups.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container group');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -0,0 +1,19 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Container list">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="azure.containerinstances" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Container instances</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<containergroups-datatable
|
||||
title="Containers" title-icon="fa-server"
|
||||
dataset="containerGroups" table-key="containergroups"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="deleteAction"
|
||||
></containergroups-datatable>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,87 @@
|
|||
angular.module('portainer.azure')
|
||||
.controller('AzureCreateContainerInstanceController', ['$q', '$scope', '$state', 'AzureService', 'Notifications',
|
||||
function ($q, $scope, $state, AzureService, Notifications) {
|
||||
|
||||
var allResourceGroups = [];
|
||||
var allProviders = [];
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
selectedSubscription: null,
|
||||
selectedResourceGroup: null
|
||||
};
|
||||
|
||||
$scope.changeSubscription = function() {
|
||||
var selectedSubscription = $scope.state.selectedSubscription;
|
||||
updateResourceGroupsAndLocations(selectedSubscription, allResourceGroups, allProviders);
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function() {
|
||||
$scope.model.Ports.push({ host: '', container: '', protocol: 'TCP' });
|
||||
};
|
||||
|
||||
$scope.removePortBinding = function(index) {
|
||||
$scope.model.Ports.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.create = function() {
|
||||
var model = $scope.model;
|
||||
var subscriptionId = $scope.state.selectedSubscription.Id;
|
||||
var resourceGroupName = $scope.state.selectedResourceGroup.Name;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
AzureService.createContainerGroup(model, subscriptionId, resourceGroupName)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Container successfully created', model.Name);
|
||||
$state.go('azure.containerinstances');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create container');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
function updateResourceGroupsAndLocations(subscription, resourceGroups, providers) {
|
||||
$scope.state.selectedResourceGroup = resourceGroups[subscription.Id][0];
|
||||
$scope.resourceGroups = resourceGroups[subscription.Id];
|
||||
|
||||
var currentSubLocations = providers[subscription.Id].Locations;
|
||||
$scope.model.Location = currentSubLocations[0];
|
||||
$scope.locations = currentSubLocations;
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var model = new ContainerGroupDefaultModel();
|
||||
|
||||
AzureService.subscriptions()
|
||||
.then(function success(data) {
|
||||
var subscriptions = data;
|
||||
$scope.state.selectedSubscription = subscriptions[0];
|
||||
$scope.subscriptions = subscriptions;
|
||||
|
||||
return $q.all({
|
||||
resourceGroups: AzureService.resourceGroups(subscriptions),
|
||||
containerInstancesProviders: AzureService.containerInstanceProvider(subscriptions)
|
||||
});
|
||||
})
|
||||
.then(function success(data) {
|
||||
var resourceGroups = data.resourceGroups;
|
||||
allResourceGroups = resourceGroups;
|
||||
|
||||
var containerInstancesProviders = data.containerInstancesProviders;
|
||||
allProviders = containerInstancesProviders;
|
||||
|
||||
$scope.model = model;
|
||||
|
||||
var selectedSubscription = $scope.state.selectedSubscription;
|
||||
updateResourceGroupsAndLocations(selectedSubscription, resourceGroups, containerInstancesProviders);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve Azure resources');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -0,0 +1,160 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Create container instance"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="azure.containerinstances">Container instances</a> > Add container
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" autocomplete="off">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Azure settings
|
||||
</div>
|
||||
<!-- subscription-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_subscription" class="col-sm-1 control-label text-left">Subscription</label>
|
||||
<div class="col-sm-11">
|
||||
<select class="form-control" name="azure_subscription" ng-model="state.selectedSubscription" ng-options="subscription.Name for subscription in subscriptions" ng-change="changeSubscription()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !subscription-input -->
|
||||
<!-- resourcegroup-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_resourcegroup" class="col-sm-1 control-label text-left">Resource group</label>
|
||||
<div class="col-sm-11">
|
||||
<select class="form-control" name="azure_resourcegroup" ng-model="state.selectedResourceGroup" ng-options="resourceGroup.Name for resourceGroup in resourceGroups"></select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !resourcegroup-input -->
|
||||
<!-- location-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_location" class="col-sm-1 control-label text-left">Location</label>
|
||||
<div class="col-sm-11">
|
||||
<select class="form-control" name="azure_location" ng-model="model.Location" ng-options="location for location in locations"></select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !location-input -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Container configuration
|
||||
</div>
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="model.Name" name="container_name" placeholder="e.g. myContainer">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- image-input -->
|
||||
<div class="form-group">
|
||||
<label for="image_name" class="col-sm-1 control-label text-left">Image</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="model.Image" name="image_name" placeholder="e.g. nginx:alpine">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !image-input -->
|
||||
<!-- os-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_os" class="col-sm-1 control-label text-left">OS</label>
|
||||
<div class="col-sm-11">
|
||||
<select class="form-control" ng-model="model.OSType" name="container_os">
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="Windows">Windows</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !os-input -->
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Port mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
||||
</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="binding in model.Ports" style="margin-top: 2px;">
|
||||
<!-- host-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="binding.host" placeholder="e.g. 80">
|
||||
</div>
|
||||
<!-- !host-port -->
|
||||
<span style="margin: 0 10px 0 10px;">
|
||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<!-- container-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="binding.container" placeholder="e.g. 80">
|
||||
</div>
|
||||
<!-- !container-port -->
|
||||
<!-- protocol-actions -->
|
||||
<div class="input-group col-sm-3 input-group-sm">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="binding.protocol" uib-btn-radio="'TCP'">TCP</label>
|
||||
<label class="btn btn-primary" ng-model="binding.protocol" uib-btn-radio="'UDP'">UDP</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removePortBinding($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !protocol-actions -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- public-ip -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="public_ip" class="control-label text-left">
|
||||
Allocate public IP address
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="public_ip" ng-model="model.AllocatePublicIP"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- public-ip -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Container resources
|
||||
</div>
|
||||
<!-- cpu-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_cpu" class="col-sm-1 control-label text-left">CPU</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="number" class="form-control" ng-model="model.CPU" name="container_cpu" placeholder="1">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !cpu-input -->
|
||||
<!-- memory-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_memory" class="col-sm-1 control-label text-left">Memory</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="number" class="form-control" ng-model="model.Memory" name="container_memory" placeholder="1">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !memory-input -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress" ng-click="create()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,33 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Home"></rd-header-title>
|
||||
<rd-header-content>Dashboard</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="subscriptions">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<a ui-sref="azure.subscriptions">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon blue pull-left">
|
||||
<i class="fa fa-th-list"></i>
|
||||
</div>
|
||||
<div class="title">{{ subscriptions.length }}</div>
|
||||
<div class="comment">Subscriptions</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6" ng-if="resourceGroups">
|
||||
<a ui-sref="azure.resourceGroups">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon blue pull-left">
|
||||
<i class="fa fa-th-list"></i>
|
||||
</div>
|
||||
<div class="title">{{ resourceGroups.length }}</div>
|
||||
<div class="comment">Resource groups</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
angular.module('portainer.azure')
|
||||
.controller('AzureDashboardController', ['$scope', 'AzureService', 'Notifications',
|
||||
function ($scope, AzureService, Notifications) {
|
||||
|
||||
function initView() {
|
||||
AzureService.subscriptions()
|
||||
.then(function success(data) {
|
||||
var subscriptions = data;
|
||||
$scope.subscriptions = subscriptions;
|
||||
return AzureService.resourceGroups(subscriptions);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.resourceGroups = AzureService.aggregate(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to load dashboard data');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -144,7 +144,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co
|
|||
$scope.state.joinNetworkInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.commit = function () {
|
||||
var image = $scope.config.Image;
|
||||
var registry = $scope.config.Registry;
|
||||
|
|
|
@ -70,7 +70,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
service.createLocalEndpoint = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.createEndpoint('local', 'unix:///var/run/docker.sock', '', 1, false)
|
||||
FileUploadService.createEndpoint('local', 1, 'unix:///var/run/docker.sock', '', 1, false)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
|
@ -81,10 +81,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createRemoteEndpoint = function(name, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.createEndpoint(name, 'tcp://' + URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
FileUploadService.createEndpoint(name, type, 'tcp://' + URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
|
@ -95,5 +95,19 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to connect to Azure', err: err});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
|
|
@ -42,11 +42,12 @@ angular.module('portainer.app')
|
|||
});
|
||||
};
|
||||
|
||||
service.createEndpoint = function(name, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
service.createEndpoint = function(name, type, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointType: type,
|
||||
URL: URL,
|
||||
PublicURL: PublicURL,
|
||||
GroupID: groupID,
|
||||
|
@ -61,6 +62,20 @@ angular.module('portainer.app')
|
|||
});
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointType: 3,
|
||||
AzureApplicationID: applicationId,
|
||||
AzureTenantID: tenantId,
|
||||
AzureAuthenticationKey: authenticationKey
|
||||
},
|
||||
ignoreLoadingBar: true
|
||||
});
|
||||
};
|
||||
|
||||
service.uploadLDAPTLSFiles = function(TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var queue = [];
|
||||
|
||||
|
|
|
@ -128,6 +128,14 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
|||
if (loading) {
|
||||
state.loading = true;
|
||||
}
|
||||
|
||||
if (type === 3) {
|
||||
state.endpoint.mode = { provider: 'AZURE' };
|
||||
LocalStorage.storeEndpointState(state.endpoint);
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
$q.all({
|
||||
version: SystemService.version(),
|
||||
info: SystemService.info()
|
||||
|
|
|
@ -13,12 +13,7 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica
|
|||
AuthenticationError: ''
|
||||
};
|
||||
|
||||
function setActiveEndpointAndRedirectToDashboard(endpoint) {
|
||||
var endpointID = EndpointProvider.endpointID();
|
||||
if (!endpointID) {
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
}
|
||||
|
||||
function redirectToDockerDashboard(endpoint) {
|
||||
ExtensionManager.initEndpointExtensions(endpoint.Id)
|
||||
.then(function success(data) {
|
||||
var extensions = data;
|
||||
|
@ -32,12 +27,31 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica
|
|||
});
|
||||
}
|
||||
|
||||
function redirectToAzureDashboard(endpoint) {
|
||||
StateManager.updateEndpointState(false, endpoint.Type, [])
|
||||
.then(function success(data) {
|
||||
$state.go('azure.dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
||||
});
|
||||
}
|
||||
|
||||
function redirectToDashboard(endpoint) {
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
|
||||
if (endpoint.Type === 3) {
|
||||
return redirectToAzureDashboard(endpoint);
|
||||
}
|
||||
redirectToDockerDashboard(endpoint);
|
||||
}
|
||||
|
||||
function unauthenticatedFlow() {
|
||||
EndpointService.endpoints()
|
||||
.then(function success(data) {
|
||||
var endpoints = data;
|
||||
if (endpoints.length > 0) {
|
||||
setActiveEndpointAndRedirectToDashboard(endpoints[0]);
|
||||
redirectToDashboard(endpoints[0]);
|
||||
} else {
|
||||
$state.go('portainer.init.endpoint');
|
||||
}
|
||||
|
@ -79,7 +93,7 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica
|
|||
var endpoints = data;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
if (endpoints.length > 0) {
|
||||
setActiveEndpointAndRedirectToDashboard(endpoints[0]);
|
||||
redirectToDashboard(endpoints[0]);
|
||||
} else if (endpoints.length === 0 && userDetails.role === 1) {
|
||||
$state.go('portainer.init.endpoint');
|
||||
} else if (endpoints.length === 0 && userDetails.role === 2) {
|
||||
|
|
|
@ -12,7 +12,10 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
|
|||
URL: '',
|
||||
PublicURL: '',
|
||||
GroupId: 1,
|
||||
SecurityFormData: new EndpointSecurityFormData()
|
||||
SecurityFormData: new EndpointSecurityFormData(),
|
||||
AzureApplicationId: '',
|
||||
AzureTenantId: '',
|
||||
AzureAuthenticationKey: ''
|
||||
};
|
||||
|
||||
$scope.addDockerEndpoint = function() {
|
||||
|
@ -30,7 +33,7 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
|
|||
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
|
||||
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
|
||||
|
||||
addEndpoint(name, URL, publicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
addEndpoint(name, 1, URL, publicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
};
|
||||
|
||||
$scope.addAgentEndpoint = function() {
|
||||
|
@ -39,12 +42,38 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
|
|||
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
|
||||
addEndpoint(name, URL, publicURL, groupId, true, true, true, null, null, null);
|
||||
addEndpoint(name, 2, URL, publicURL, groupId, true, true, true, null, null, null);
|
||||
};
|
||||
|
||||
function addEndpoint(name, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
$scope.addAzureEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var applicationId = $scope.formValues.AzureApplicationId;
|
||||
var tenantId = $scope.formValues.AzureTenantId;
|
||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
||||
|
||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey);
|
||||
};
|
||||
|
||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) {
|
||||
var endpoint;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
|
||||
.then(function success() {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function addEndpoint(name, type, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success() {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
|
|
|
@ -36,6 +36,16 @@
|
|||
<p>Portainer agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
|
||||
<label for="azure_endpoint">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Azure
|
||||
</div>
|
||||
<p>Connect to Microsoft Azure</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'docker'">
|
||||
|
@ -59,6 +69,28 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'azure'">
|
||||
<div class="col-sm-12 form-section-title" >
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental.
|
||||
</p>
|
||||
<p class="text-primary">
|
||||
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
|
||||
</p>
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Have a look at <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank">the Azure documentation</a> to retrieve
|
||||
the credentials required below.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment details
|
||||
</div>
|
||||
|
@ -78,35 +110,88 @@
|
|||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- endpoint-url-input -->
|
||||
<div class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Endpoint URL
|
||||
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input ng-if="state.EnvironmentType === 'docker'" type="text" class="form-control" name="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375" required>
|
||||
<input ng-if="state.EnvironmentType === 'agent'" type="text" class="form-control" name="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001" required>
|
||||
<div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'">
|
||||
<div class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Endpoint URL
|
||||
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input ng-if="state.EnvironmentType === 'docker'" type="text" class="form-control" name="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375" required>
|
||||
<input ng-if="state.EnvironmentType === 'agent'" type="text" class="form-control" name="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="endpointCreationForm.endpoint_url.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="endpointCreationForm.endpoint_url.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
<div class="form-group" ng-show="endpointCreationForm.endpoint_url.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="endpointCreationForm.endpoint_url.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !endpoint-url-input -->
|
||||
<!-- endpoint-public-url-input -->
|
||||
<div class="form-group">
|
||||
<label for="endpoint_public_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Public IP
|
||||
<portainer-tooltip position="bottom" message="URL or IP address where exposed containers will be reachable. This field is optional and will default to the endpoint URL."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="endpoint_public_url" ng-model="formValues.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com">
|
||||
<div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'">
|
||||
<div class="form-group">
|
||||
<label for="endpoint_public_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Public IP
|
||||
<portainer-tooltip position="bottom" message="URL or IP address where exposed containers will be reachable. This field is optional and will default to the endpoint URL."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="endpoint_public_url" ng-model="formValues.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !endpoint-public-url-input -->
|
||||
<!-- azure-details -->
|
||||
<div ng-if="state.EnvironmentType === 'azure'">
|
||||
<!-- applicationId-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_appid" class="col-sm-3 col-lg-2 control-label text-left">Application ID</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="azure_credential_appid" ng-model="formValues.AzureApplicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_appid.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="endpointCreationForm.azure_credential_appid.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !applicationId-input -->
|
||||
<!-- tenantId-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_tenantid" class="col-sm-3 col-lg-2 control-label text-left">Tenant ID</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="azure_credential_tenantid" ng-model="formValues.AzureTenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_tenantid.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="endpointCreationForm.azure_credential_tenantid.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tenantId-input -->
|
||||
<!-- authenticationkey-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_authkey" class="col-sm-3 col-lg-2 control-label text-left">Authentication key</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="azure_credential_authkey" ng-model="formValues.AzureAuthenticationKey" placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk=" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_authkey.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="endpointCreationForm.azure_credential_authkey.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !authenticationkey-input -->
|
||||
</div>
|
||||
<!-- !azure-details -->
|
||||
<!-- group -->
|
||||
<div class="form-group">
|
||||
<label for="endpoint_group" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
|
@ -131,6 +216,10 @@
|
|||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
</button>
|
||||
<button ng-if="state.EnvironmentType === 'azure'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addAzureEndpoint()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="page-wrapper">
|
||||
<!-- simple box -->
|
||||
<div class="container simple-box">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<div class="col-sm-12">
|
||||
<!-- simple box logo -->
|
||||
<div class="row">
|
||||
<img ng-if="logo" ng-src="{{ logo }}" class="simple-box-logo">
|
||||
|
@ -55,6 +55,16 @@
|
|||
<p>Connect to a Portainer agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="azure_endpoint" ng-model="formValues.EndpointType" value="azure">
|
||||
<label for="azure_endpoint">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Azure
|
||||
</div>
|
||||
<p>Connect to Microsoft Azure</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !endpoint-type -->
|
||||
|
@ -141,6 +151,78 @@
|
|||
<!-- !actions -->
|
||||
</div>
|
||||
<!-- !agent-endpoint -->
|
||||
<!-- azure-endpoint -->
|
||||
<div ng-if="formValues.EndpointType === 'azure'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental.
|
||||
</p>
|
||||
<p class="text-primary">
|
||||
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
|
||||
</p>
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Have a look at <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank">the Azure documentation</a> to retrieve
|
||||
the credentials required below.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment
|
||||
</div>
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. azure-01">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Azure credentials
|
||||
</div>
|
||||
<!-- applicationId-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_appid" class="col-sm-4 col-lg-3 control-label text-left">Application ID</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input type="text" class="form-control" id="azure_credential_appid" ng-model="formValues.AzureApplicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !applicationId-input -->
|
||||
<!-- tenantId-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_tenantid" class="col-sm-4 col-lg-3 control-label text-left">Tenant ID</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input type="text" class="form-control" id="azure_credential_tenantid" ng-model="formValues.AzureTenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tenantId-input -->
|
||||
<!-- authenticationkey-input -->
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_authkey" class="col-sm-4 col-lg-3 control-label text-left">Authentication key</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input type="text" class="form-control" id="azure_credential_authkey" ng-model="formValues.AzureAuthenticationKey" placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk=">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !authenticationkey-input -->
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.Name || !formValues.AzureApplicationId || !formValues.AzureTenantId || !formValues.AzureAuthenticationKey" ng-click="createAzureEndpoint()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
|
||||
<span ng-show="state.actionInProgress">Connecting...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</div>
|
||||
<!-- !azure-endpoint -->
|
||||
<!-- remote-endpoint -->
|
||||
<div ng-if="formValues.EndpointType === 'remote'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
|
|
|
@ -22,7 +22,10 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
TLSSKipClientVerify: false,
|
||||
TLSCACert: null,
|
||||
TLSCert: null,
|
||||
TLSKey: null
|
||||
TLSKey: null,
|
||||
AzureApplicationId: '',
|
||||
AzureTenantId: '',
|
||||
AzureAuthenticationKey: ''
|
||||
};
|
||||
|
||||
$scope.createLocalEndpoint = function() {
|
||||
|
@ -52,12 +55,21 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
});
|
||||
};
|
||||
|
||||
$scope.createAzureEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var applicationId = $scope.formValues.AzureApplicationId;
|
||||
var tenantId = $scope.formValues.AzureTenantId;
|
||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
||||
|
||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey);
|
||||
};
|
||||
|
||||
$scope.createAgentEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var URL = $scope.formValues.URL;
|
||||
var PublicURL = URL.split(':')[0];
|
||||
|
||||
createRemoteEndpoint(name, URL, PublicURL, true, true, true, null, null, null);
|
||||
createRemoteEndpoint(name, 2, URL, PublicURL, true, true, true, null, null, null);
|
||||
};
|
||||
|
||||
$scope.createRemoteEndpoint = function() {
|
||||
|
@ -71,13 +83,34 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
var TLSCertFile = TLSSKipClientVerify ? null : $scope.formValues.TLSCert;
|
||||
var TLSKeyFile = TLSSKipClientVerify ? null : $scope.formValues.TLSKey;
|
||||
|
||||
createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
createRemoteEndpoint(name, 1, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
};
|
||||
|
||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) {
|
||||
var endpoint;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
|
||||
.then(function success(data) {
|
||||
endpoint = data;
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
return StateManager.updateEndpointState(false, endpoint.Type, []);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$state.go('azure.dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to connect to the Azure environment');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var endpoint;
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, 1, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(data) {
|
||||
endpoint = data;
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
select-endpoint="switchEndpoint"
|
||||
></endpoint-selector>
|
||||
<li class="sidebar-title"><span class="endpoint-name">{{ activeEndpoint.Name }}</span></li>
|
||||
<docker-sidebar-content
|
||||
<azure-sidebar-content ng-if="applicationState.endpoint.mode.provider === 'AZURE'">
|
||||
</azure-sidebar-content>
|
||||
<docker-sidebar-content ng-if="applicationState.endpoint.mode.provider !== 'AZURE'"
|
||||
endpoint-api-version="applicationState.endpoint.apiVersion"
|
||||
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
||||
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'"
|
||||
|
|
|
@ -5,6 +5,29 @@ function ($q, $scope, $state, EndpointService, GroupService, StateManager, Endpo
|
|||
$scope.switchEndpoint = function(endpoint) {
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
if (endpoint.Type === 3) {
|
||||
switchToAzureEndpoint(endpoint);
|
||||
} else {
|
||||
switchToDockerEndpoint(endpoint);
|
||||
}
|
||||
};
|
||||
|
||||
function switchToAzureEndpoint(endpoint) {
|
||||
StateManager.updateEndpointState(false, endpoint.Type, [])
|
||||
.then(function success() {
|
||||
$scope.currentEndpoint = endpoint;
|
||||
$state.go('azure.dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
||||
var currentEndpoint = $scope.currentEndpoint;
|
||||
EndpointProvider.setEndpointID(currentEndpoint.Id);
|
||||
EndpointProvider.setEndpointPublicURL(currentEndpoint.PublicURL);
|
||||
return StateManager.updateEndpointState(true, currentEndpoint.Type, currentEndpoint.Extensions);
|
||||
});
|
||||
}
|
||||
|
||||
function switchToDockerEndpoint(endpoint) {
|
||||
ExtensionManager.initEndpointExtensions(endpoint.Id)
|
||||
.then(function success(data) {
|
||||
var extensions = data;
|
||||
|
@ -21,7 +44,7 @@ function ($q, $scope, $state, EndpointService, GroupService, StateManager, Endpo
|
|||
EndpointProvider.setEndpointPublicURL(currentEndpoint.PublicURL);
|
||||
return StateManager.updateEndpointState(true, currentEndpoint.Type, currentEndpoint.Extensions);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function setActiveEndpoint(endpoints) {
|
||||
var activeEndpointID = EndpointProvider.endpointID();
|
||||
|
|
Loading…
Reference in New Issue