feat(templates): remove template management features (#3719)
* feat(api): remove template management features * feat(templates): remove template management features2.0
parent
45f93882d0
commit
5563ff60fc
|
@ -26,7 +26,6 @@ import (
|
|||
"github.com/portainer/portainer/api/bolt/tag"
|
||||
"github.com/portainer/portainer/api/bolt/team"
|
||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||
"github.com/portainer/portainer/api/bolt/template"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
"github.com/portainer/portainer/api/bolt/webhook"
|
||||
|
@ -58,7 +57,6 @@ type Store struct {
|
|||
TagService *tag.Service
|
||||
TeamMembershipService *teammembership.Service
|
||||
TeamService *team.Service
|
||||
TemplateService *template.Service
|
||||
TunnelServerService *tunnelserver.Service
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
|
@ -137,7 +135,6 @@ func (store *Store) MigrateData() error {
|
|||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
TemplateService: store.TemplateService,
|
||||
UserService: store.UserService,
|
||||
VersionService: store.VersionService,
|
||||
FileService: store.fileService,
|
||||
|
@ -246,12 +243,6 @@ func (store *Store) initServices() error {
|
|||
}
|
||||
store.TeamService = teamService
|
||||
|
||||
templateService, err := template.NewService(store.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.TemplateService = templateService
|
||||
|
||||
tunnelServerService, err := tunnelserver.NewService(store.db)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
package migrator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion15() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
|
@ -17,19 +11,6 @@ func (m *Migrator) updateSettingsToDBVersion15() error {
|
|||
}
|
||||
|
||||
func (m *Migrator) updateTemplatesToVersion15() error {
|
||||
legacyTemplates, err := m.templateService.Templates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, template := range legacyTemplates {
|
||||
template.Logo = strings.Replace(template.Logo, "https://portainer.io/images", portainer.AssetsServerURL, -1)
|
||||
|
||||
err = m.templateService.UpdateTemplate(template.ID, &template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Removed with the entire template management layer, part of https://github.com/portainer/portainer/issues/3707
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/portainer/portainer/api/bolt/stack"
|
||||
"github.com/portainer/portainer/api/bolt/tag"
|
||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||
"github.com/portainer/portainer/api/bolt/template"
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
)
|
||||
|
@ -37,7 +36,6 @@ type (
|
|||
stackService *stack.Service
|
||||
tagService *tag.Service
|
||||
teamMembershipService *teammembership.Service
|
||||
templateService *template.Service
|
||||
userService *user.Service
|
||||
versionService *version.Service
|
||||
fileService portainer.FileService
|
||||
|
@ -59,7 +57,6 @@ type (
|
|||
StackService *stack.Service
|
||||
TagService *tag.Service
|
||||
TeamMembershipService *teammembership.Service
|
||||
TemplateService *template.Service
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
FileService portainer.FileService
|
||||
|
@ -82,7 +79,6 @@ func NewMigrator(parameters *Parameters) *Migrator {
|
|||
settingsService: parameters.SettingsService,
|
||||
tagService: parameters.TagService,
|
||||
teamMembershipService: parameters.TeamMembershipService,
|
||||
templateService: parameters.TemplateService,
|
||||
stackService: parameters.StackService,
|
||||
userService: parameters.UserService,
|
||||
versionService: parameters.VersionService,
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "templates"
|
||||
)
|
||||
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(db *bolt.DB) (*Service, error) {
|
||||
err := internal.CreateBucket(db, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Templates return an array containing all the templates.
|
||||
func (service *Service) Templates() ([]portainer.Template, error) {
|
||||
var templates = make([]portainer.Template, 0)
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var template portainer.Template
|
||||
err := internal.UnmarshalObject(v, &template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
templates = append(templates, template)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return templates, err
|
||||
}
|
||||
|
||||
// Template returns a template by ID.
|
||||
func (service *Service) Template(ID portainer.TemplateID) (*portainer.Template, error) {
|
||||
var template portainer.Template
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.db, BucketName, identifier, &template)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
}
|
||||
|
||||
// CreateTemplate creates a new template.
|
||||
func (service *Service) CreateTemplate(template *portainer.Template) error {
|
||||
return service.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
template.ID = portainer.TemplateID(id)
|
||||
|
||||
data, err := internal.MarshalObject(template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(template.ID)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTemplate saves a template.
|
||||
func (service *Service) UpdateTemplate(ID portainer.TemplateID, template *portainer.Template) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.db, BucketName, identifier, template)
|
||||
}
|
||||
|
||||
// DeleteTemplate deletes a template.
|
||||
func (service *Service) DeleteTemplate(ID portainer.TemplateID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
||||
}
|
|
@ -20,7 +20,6 @@ const (
|
|||
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
||||
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
|
||||
errTemplateFileNotFound = portainer.Error("Unable to locate template file on disk")
|
||||
errInvalidSyncInterval = portainer.Error("Invalid synchronization interval")
|
||||
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
||||
errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints")
|
||||
|
@ -58,7 +57,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
||||
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
||||
Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(),
|
||||
TemplateFile: kingpin.Flag("template-file", "Path to the App templates definitions on the filesystem (deprecated)").Default(defaultTemplateFile).String(),
|
||||
}
|
||||
|
||||
kingpin.Parse()
|
||||
|
@ -83,12 +81,7 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
|||
return errEndpointExcludeExternal
|
||||
}
|
||||
|
||||
err := validateTemplateFile(*flags.TemplateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = validateEndpointURL(*flags.EndpointURL)
|
||||
err := validateEndpointURL(*flags.EndpointURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -173,16 +166,6 @@ func validateExternalEndpoints(externalEndpoints string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateTemplateFile(templateFile string) error {
|
||||
if _, err := os.Stat(templateFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errTemplateFileNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSyncInterval(syncInterval string) error {
|
||||
if syncInterval != defaultSyncInterval {
|
||||
_, err := time.ParseDuration(syncInterval)
|
||||
|
|
|
@ -21,5 +21,4 @@ const (
|
|||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
)
|
||||
|
|
|
@ -19,5 +19,4 @@ const (
|
|||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -276,6 +275,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
|||
EnableHostManagementFeatures: false,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
|
@ -296,45 +296,6 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
|||
return nil
|
||||
}
|
||||
|
||||
func initTemplates(templateService portainer.TemplateService, fileService portainer.FileService, templateURL, templateFile string) error {
|
||||
if templateURL != "" {
|
||||
log.Printf("Portainer started with the --templates flag. Using external templates, template management will be disabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
existingTemplates, err := templateService.Templates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(existingTemplates) != 0 {
|
||||
log.Printf("Templates already registered inside the database. Skipping template import.")
|
||||
return nil
|
||||
}
|
||||
|
||||
templatesJSON, err := fileService.GetFileContent(templateFile)
|
||||
if err != nil {
|
||||
log.Println("Unable to retrieve template definitions via filesystem")
|
||||
return err
|
||||
}
|
||||
|
||||
var templates []portainer.Template
|
||||
err = json.Unmarshal(templatesJSON, &templates)
|
||||
if err != nil {
|
||||
log.Println("Unable to parse templates file. Please review your template definition file.")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, template := range templates {
|
||||
err := templateService.CreateTemplate(&template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func retrieveFirstEndpointFromDatabase(endpointService portainer.EndpointService) *portainer.Endpoint {
|
||||
endpoints, err := endpointService.Endpoints()
|
||||
if err != nil {
|
||||
|
@ -561,11 +522,6 @@ func main() {
|
|||
|
||||
composeStackManager := initComposeStackManager(*flags.Data, reverseTunnelService)
|
||||
|
||||
err = initTemplates(store.TemplateService, fileService, *flags.Templates, *flags.TemplateFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = initSettings(store.SettingsService, flags)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -674,7 +630,6 @@ func main() {
|
|||
StackService: store.StackService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
TagService: store.TagService,
|
||||
TemplateService: store.TemplateService,
|
||||
WebhookService: store.WebhookService,
|
||||
SwarmStackManager: swarmStackManager,
|
||||
ComposeStackManager: composeStackManager,
|
||||
|
|
|
@ -175,6 +175,7 @@ github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2 h1:0PfgGLys9yH
|
|||
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2/go.mod h1:/wIeGwJOMYc1JplE/OvYMO5korce39HddIfI8VKGyAM=
|
||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
|
||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
|
||||
github.com/portainer/portainer v0.10.1 h1:I8K345CjGWfUGsVA8c8/gqamwLCC6CIAjxZXSklAFq0=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||
|
|
|
@ -18,7 +18,7 @@ func (handler *Handler) edgeTemplateList(w http.ResponseWriter, r *http.Request)
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
url := portainer.EdgeTemplatesURL
|
||||
url := portainer.DefaultTemplatesURL
|
||||
if settings.TemplatesURL != "" {
|
||||
url = settings.TemplatesURL
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ type publicSettingsResponse struct {
|
|||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
}
|
||||
|
||||
|
@ -36,7 +35,6 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
ExternalTemplates: false,
|
||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||
settings.OAuthSettings.AuthorizationURI,
|
||||
settings.OAuthSettings.ClientID,
|
||||
|
@ -44,9 +42,5 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
settings.OAuthSettings.Scopes),
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
publicSettings.ExternalTemplates = true
|
||||
}
|
||||
|
||||
return response.JSON(w, publicSettings)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,9 @@ import (
|
|||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
const (
|
||||
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
|
||||
)
|
||||
|
||||
// Handler represents an HTTP API handler for managing templates.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
TemplateService portainer.TemplateService
|
||||
SettingsService portainer.SettingsService
|
||||
}
|
||||
|
||||
|
@ -28,29 +23,5 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
|
||||
h.Handle("/templates",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
|
||||
h.Handle("/templates",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.RestrictedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) templateManagementCheck(next http.Handler) http.Handler {
|
||||
return httperror.LoggerHandler(func(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
settings, err := handler.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Portainer is configured to use external templates, template management is disabled", errTemplateManagementDisabled}
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
type templateCreatePayload struct {
|
||||
// Mandatory
|
||||
Type int
|
||||
Title string
|
||||
Description string
|
||||
AdministratorOnly bool
|
||||
|
||||
// Opt stack/container
|
||||
Name string
|
||||
Logo string
|
||||
Note string
|
||||
Platform string
|
||||
Categories []string
|
||||
Env []portainer.TemplateEnv
|
||||
|
||||
// Mandatory container
|
||||
Image string
|
||||
|
||||
// Mandatory stack
|
||||
Repository portainer.TemplateRepository
|
||||
|
||||
// Opt container
|
||||
Registry string
|
||||
Command string
|
||||
Network string
|
||||
Volumes []portainer.TemplateVolume
|
||||
Ports []string
|
||||
Labels []portainer.Pair
|
||||
Privileged bool
|
||||
Interactive bool
|
||||
RestartPolicy string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
func (payload *templateCreatePayload) Validate(r *http.Request) error {
|
||||
if payload.Type == 0 || (payload.Type != 1 && payload.Type != 2 && payload.Type != 3) {
|
||||
return portainer.Error("Invalid template type. Valid values are: 1 (container), 2 (Swarm stack template) or 3 (Compose stack template).")
|
||||
}
|
||||
if govalidator.IsNull(payload.Title) {
|
||||
return portainer.Error("Invalid template title")
|
||||
}
|
||||
if govalidator.IsNull(payload.Description) {
|
||||
return portainer.Error("Invalid template description")
|
||||
}
|
||||
|
||||
if payload.Type == 1 {
|
||||
if govalidator.IsNull(payload.Image) {
|
||||
return portainer.Error("Invalid template image")
|
||||
}
|
||||
}
|
||||
|
||||
if payload.Type == 2 || payload.Type == 3 {
|
||||
if govalidator.IsNull(payload.Repository.URL) {
|
||||
return portainer.Error("Invalid template repository URL")
|
||||
}
|
||||
if govalidator.IsNull(payload.Repository.StackFile) {
|
||||
payload.Repository.StackFile = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// POST request on /api/templates
|
||||
func (handler *Handler) templateCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload templateCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
template := &portainer.Template{
|
||||
Type: portainer.TemplateType(payload.Type),
|
||||
Title: payload.Title,
|
||||
Description: payload.Description,
|
||||
AdministratorOnly: payload.AdministratorOnly,
|
||||
Name: payload.Name,
|
||||
Logo: payload.Logo,
|
||||
Note: payload.Note,
|
||||
Platform: payload.Platform,
|
||||
Categories: payload.Categories,
|
||||
Env: payload.Env,
|
||||
}
|
||||
|
||||
if template.Type == portainer.ContainerTemplate {
|
||||
template.Image = payload.Image
|
||||
template.Registry = payload.Registry
|
||||
template.Command = payload.Command
|
||||
template.Network = payload.Network
|
||||
template.Volumes = payload.Volumes
|
||||
template.Ports = payload.Ports
|
||||
template.Labels = payload.Labels
|
||||
template.Privileged = payload.Privileged
|
||||
template.Interactive = payload.Interactive
|
||||
template.RestartPolicy = payload.RestartPolicy
|
||||
template.Hostname = payload.Hostname
|
||||
}
|
||||
|
||||
if template.Type == portainer.SwarmStackTemplate || template.Type == portainer.ComposeStackTemplate {
|
||||
template.Repository = payload.Repository
|
||||
}
|
||||
|
||||
err = handler.TemplateService.CreateTemplate(template)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the template inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// DELETE request on /api/templates/:id
|
||||
func (handler *Handler) templateDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
id, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
err = handler.TemplateService.DeleteTemplate(portainer.TemplateID(id))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the template from the database", err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// GET request on /api/templates/:id
|
||||
func (handler *Handler) templateInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
templateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
template, err := handler.TemplateService.Template(portainer.TemplateID(templateID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a template with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a template with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// GET request on /api/templates
|
||||
|
@ -18,30 +14,17 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
var templates []portainer.Template
|
||||
if settings.TemplatesURL == "" {
|
||||
templates, err = handler.TemplateService.Templates()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
|
||||
}
|
||||
} else {
|
||||
var templateData []byte
|
||||
templateData, err = client.Get(settings.TemplatesURL, 0)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err}
|
||||
}
|
||||
|
||||
err = json.Unmarshal(templateData, &templates)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse external templates", err}
|
||||
}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
resp, err := http.Get(settings.TemplatesURL)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates via the network", err}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to write templates from templates URL", err}
|
||||
}
|
||||
|
||||
filteredTemplates := security.FilterTemplates(templates, securityContext)
|
||||
return response.JSON(w, filteredTemplates)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type templateUpdatePayload struct {
|
||||
Title *string
|
||||
Description *string
|
||||
AdministratorOnly *bool
|
||||
Name *string
|
||||
Logo *string
|
||||
Note *string
|
||||
Platform *string
|
||||
Categories []string
|
||||
Env []portainer.TemplateEnv
|
||||
Image *string
|
||||
Registry *string
|
||||
Repository portainer.TemplateRepository
|
||||
Command *string
|
||||
Network *string
|
||||
Volumes []portainer.TemplateVolume
|
||||
Ports []string
|
||||
Labels []portainer.Pair
|
||||
Privileged *bool
|
||||
Interactive *bool
|
||||
RestartPolicy *string
|
||||
Hostname *string
|
||||
}
|
||||
|
||||
func (payload *templateUpdatePayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/templates/:id
|
||||
func (handler *Handler) templateUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
templateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
template, err := handler.TemplateService.Template(portainer.TemplateID(templateID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a template with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a template with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
var payload templateUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
updateTemplate(template, &payload)
|
||||
|
||||
err = handler.TemplateService.UpdateTemplate(template.ID, template)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to persist template changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
||||
|
||||
func updateContainerProperties(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Image != nil {
|
||||
template.Image = *payload.Image
|
||||
}
|
||||
|
||||
if payload.Registry != nil {
|
||||
template.Registry = *payload.Registry
|
||||
}
|
||||
|
||||
if payload.Command != nil {
|
||||
template.Command = *payload.Command
|
||||
}
|
||||
|
||||
if payload.Network != nil {
|
||||
template.Network = *payload.Network
|
||||
}
|
||||
|
||||
if payload.Volumes != nil {
|
||||
template.Volumes = payload.Volumes
|
||||
}
|
||||
|
||||
if payload.Ports != nil {
|
||||
template.Ports = payload.Ports
|
||||
}
|
||||
|
||||
if payload.Labels != nil {
|
||||
template.Labels = payload.Labels
|
||||
}
|
||||
|
||||
if payload.Privileged != nil {
|
||||
template.Privileged = *payload.Privileged
|
||||
}
|
||||
|
||||
if payload.Interactive != nil {
|
||||
template.Interactive = *payload.Interactive
|
||||
}
|
||||
|
||||
if payload.RestartPolicy != nil {
|
||||
template.RestartPolicy = *payload.RestartPolicy
|
||||
}
|
||||
|
||||
if payload.Hostname != nil {
|
||||
template.Hostname = *payload.Hostname
|
||||
}
|
||||
}
|
||||
|
||||
func updateStackProperties(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Repository.URL != "" && payload.Repository.StackFile != "" {
|
||||
template.Repository = payload.Repository
|
||||
}
|
||||
}
|
||||
|
||||
func updateTemplate(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Title != nil {
|
||||
template.Title = *payload.Title
|
||||
}
|
||||
|
||||
if payload.Description != nil {
|
||||
template.Description = *payload.Description
|
||||
}
|
||||
|
||||
if payload.Name != nil {
|
||||
template.Name = *payload.Name
|
||||
}
|
||||
|
||||
if payload.Logo != nil {
|
||||
template.Logo = *payload.Logo
|
||||
}
|
||||
|
||||
if payload.Note != nil {
|
||||
template.Note = *payload.Note
|
||||
}
|
||||
|
||||
if payload.Platform != nil {
|
||||
template.Platform = *payload.Platform
|
||||
}
|
||||
|
||||
if payload.Categories != nil {
|
||||
template.Categories = payload.Categories
|
||||
}
|
||||
|
||||
if payload.Env != nil {
|
||||
template.Env = payload.Env
|
||||
}
|
||||
|
||||
if payload.AdministratorOnly != nil {
|
||||
template.AdministratorOnly = *payload.AdministratorOnly
|
||||
}
|
||||
|
||||
if template.Type == portainer.ContainerTemplate {
|
||||
updateContainerProperties(template, payload)
|
||||
} else if template.Type == portainer.SwarmStackTemplate || template.Type == portainer.ComposeStackTemplate {
|
||||
updateStackProperties(template, payload)
|
||||
}
|
||||
}
|
|
@ -79,24 +79,6 @@ func FilterRegistries(registries []portainer.Registry, context *RestrictedReques
|
|||
return filteredRegistries
|
||||
}
|
||||
|
||||
// FilterTemplates filters templates based on the user role.
|
||||
// Non-administrator template do not have access to templates where the AdministratorOnly flag is set to true.
|
||||
func FilterTemplates(templates []portainer.Template, context *RestrictedRequestContext) []portainer.Template {
|
||||
filteredTemplates := templates
|
||||
|
||||
if !context.IsAdmin {
|
||||
filteredTemplates = make([]portainer.Template, 0)
|
||||
|
||||
for _, template := range templates {
|
||||
if !template.AdministratorOnly {
|
||||
filteredTemplates = append(filteredTemplates, template)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTemplates
|
||||
}
|
||||
|
||||
// FilterEndpoints filters endpoints based on user role and team memberships.
|
||||
// Non administrator users only have access to authorized endpoints (can be inherited via endoint groups).
|
||||
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||
|
|
|
@ -78,7 +78,6 @@ type Server struct {
|
|||
TagService portainer.TagService
|
||||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
TemplateService portainer.TemplateService
|
||||
UserService portainer.UserService
|
||||
WebhookService portainer.WebhookService
|
||||
Handler *handler.Handler
|
||||
|
@ -282,7 +281,6 @@ func (server *Server) Start() error {
|
|||
var supportHandler = support.NewHandler(requestBouncer)
|
||||
|
||||
var templatesHandler = templates.NewHandler(requestBouncer)
|
||||
templatesHandler.TemplateService = server.TemplateService
|
||||
templatesHandler.SettingsService = server.SettingsService
|
||||
|
||||
var uploadHandler = upload.NewHandler(requestBouncer)
|
||||
|
|
|
@ -538,7 +538,8 @@ type (
|
|||
AccessLevel ResourceAccessLevel `json:"AccessLevel"`
|
||||
}
|
||||
|
||||
// Template represents an application template
|
||||
// Template represents an application template that can be used as an App Template
|
||||
// or an Edge template
|
||||
Template struct {
|
||||
// Mandatory container/stack fields
|
||||
ID TemplateID `json:"Id"`
|
||||
|
@ -553,7 +554,7 @@ type (
|
|||
// Mandatory stack fields
|
||||
Repository TemplateRepository `json:"repository"`
|
||||
|
||||
// Mandatory edge stack fields
|
||||
// Mandatory Edge stack fields
|
||||
StackFile string `json:"stackFile"`
|
||||
|
||||
// Optional stack/container fields
|
||||
|
@ -943,15 +944,6 @@ type (
|
|||
DeleteTeamMembershipByTeamID(teamID TeamID) error
|
||||
}
|
||||
|
||||
// TemplateService represents a service for managing template data
|
||||
TemplateService interface {
|
||||
Templates() ([]Template, error)
|
||||
Template(ID TemplateID) (*Template, error)
|
||||
CreateTemplate(template *Template) error
|
||||
UpdateTemplate(ID TemplateID, template *Template) error
|
||||
DeleteTemplate(ID TemplateID) error
|
||||
}
|
||||
|
||||
// TunnelServerService represents a service for managing data associated to the tunnel server
|
||||
TunnelServerService interface {
|
||||
Info() (*TunnelServerInfo, error)
|
||||
|
@ -1039,8 +1031,8 @@ const (
|
|||
DefaultEdgeAgentCheckinIntervalInSeconds = 5
|
||||
// LocalExtensionManifestFile represents the name of the local manifest file for extensions
|
||||
LocalExtensionManifestFile = "/extensions.json"
|
||||
// EdgeTemplatesURL represents the URL used to retrieve Edge templates
|
||||
EdgeTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-1.20.0.json"
|
||||
// DefaultTemplatesURL represents the URL to the official templates supported by Portainer
|
||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -533,28 +533,6 @@ angular.module('portainer.app', []).config([
|
|||
},
|
||||
};
|
||||
|
||||
var template = {
|
||||
name: 'portainer.templates.template',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/templates/edit/template.html',
|
||||
controller: 'TemplateController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var templateCreation = {
|
||||
name: 'portainer.templates.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/templates/create/createtemplate.html',
|
||||
controller: 'CreateTemplateController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(root);
|
||||
$stateRegistryProvider.register(portainer);
|
||||
$stateRegistryProvider.register(about);
|
||||
|
@ -595,7 +573,5 @@ angular.module('portainer.app', []).config([
|
|||
$stateRegistryProvider.register(teams);
|
||||
$stateRegistryProvider.register(team);
|
||||
$stateRegistryProvider.register(templates);
|
||||
$stateRegistryProvider.register(template);
|
||||
$stateRegistryProvider.register(templateCreation);
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -3,8 +3,5 @@ angular.module('portainer.app').component('templateItem', {
|
|||
bindings: {
|
||||
model: '=',
|
||||
onSelect: '<',
|
||||
onDelete: '<',
|
||||
showUpdateAction: '<',
|
||||
showDeleteAction: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -28,15 +28,6 @@
|
|||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="text-small">
|
||||
<a ui-sref="portainer.templates.template({ id: $ctrl.model.Id })" class="btn btn-xs btn-primary" ng-click="$event.stopPropagation();" ng-if="$ctrl.showUpdateAction">
|
||||
<i class="fa fa-edit" aria-hidden="true"></i>
|
||||
Update
|
||||
</a>
|
||||
<btn class="btn btn-xs btn-danger" ng-click="$event.stopPropagation(); $ctrl.onDelete($ctrl.model)" ng-if="$ctrl.showDeleteAction">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i> Delete
|
||||
</btn>
|
||||
</span>
|
||||
</div>
|
||||
<!-- !blocklist-item-line1 -->
|
||||
<!-- blocklist-item-line2 -->
|
||||
|
|
|
@ -7,10 +7,6 @@ angular.module('portainer.app').component('templateList', {
|
|||
templates: '<',
|
||||
tableKey: '@',
|
||||
selectAction: '<',
|
||||
deleteAction: '<',
|
||||
showSwarmStacks: '<',
|
||||
showAddAction: '<',
|
||||
showUpdateAction: '<',
|
||||
showDeleteAction: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -49,10 +49,7 @@
|
|||
<template-item
|
||||
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
||||
model="template"
|
||||
show-update-action="$ctrl.showUpdateAction"
|
||||
show-delete-action="$ctrl.showDeleteAction"
|
||||
on-select="($ctrl.selectAction)"
|
||||
on-delete="($ctrl.deleteAction)"
|
||||
></template-item>
|
||||
<div ng-if="!$ctrl.templates" class="text-center text-muted">
|
||||
Loading...
|
||||
|
|
|
@ -9,7 +9,6 @@ export function SettingsViewModel(data) {
|
|||
this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
|
||||
this.SnapshotInterval = data.SnapshotInterval;
|
||||
this.TemplatesURL = data.TemplatesURL;
|
||||
this.ExternalTemplates = data.ExternalTemplates;
|
||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
||||
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
||||
|
@ -21,7 +20,6 @@ export function PublicSettingsViewModel(settings) {
|
|||
this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
|
||||
this.AuthenticationMethod = settings.AuthenticationMethod;
|
||||
this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
||||
this.ExternalTemplates = settings.ExternalTemplates;
|
||||
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
||||
this.LogoURL = settings.LogoURL;
|
||||
this.OAuthLoginURI = settings.OAuthLoginURI;
|
||||
|
|
|
@ -1,58 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
export function TemplateDefaultModel() {
|
||||
this.Type = 1;
|
||||
this.AdministratorOnly = false;
|
||||
this.Title = '';
|
||||
this.Description = '';
|
||||
this.Volumes = [];
|
||||
this.Ports = [];
|
||||
this.Env = [];
|
||||
this.Labels = [];
|
||||
this.RestartPolicy = 'always';
|
||||
this.RegistryModel = new PorImageRegistryModel();
|
||||
}
|
||||
|
||||
export function TemplateCreateRequest(model) {
|
||||
this.Type = model.Type;
|
||||
this.Name = model.Name;
|
||||
this.Hostname = model.Hostname;
|
||||
this.Title = model.Title;
|
||||
this.Description = model.Description;
|
||||
this.Note = model.Note;
|
||||
this.Categories = model.Categories;
|
||||
this.Platform = model.Platform;
|
||||
this.Logo = model.Logo;
|
||||
this.Image = model.RegistryModel.Image;
|
||||
this.Registry = model.RegistryModel.Registry.URL;
|
||||
this.Command = model.Command;
|
||||
this.Network = model.Network && model.Network.Name;
|
||||
this.Privileged = model.Privileged;
|
||||
this.Interactive = model.Interactive;
|
||||
this.RestartPolicy = model.RestartPolicy;
|
||||
this.Labels = model.Labels;
|
||||
this.Repository = model.Repository;
|
||||
this.Env = model.Env;
|
||||
this.AdministratorOnly = model.AdministratorOnly;
|
||||
|
||||
this.Ports = [];
|
||||
for (var i = 0; i < model.Ports.length; i++) {
|
||||
var binding = model.Ports[i];
|
||||
if (binding.containerPort && binding.protocol) {
|
||||
var port = binding.hostPort ? binding.hostPort + ':' + binding.containerPort + '/' + binding.protocol : binding.containerPort + '/' + binding.protocol;
|
||||
this.Ports.push(port);
|
||||
}
|
||||
}
|
||||
|
||||
this.Volumes = model.Volumes;
|
||||
}
|
||||
|
||||
export function TemplateUpdateRequest(model) {
|
||||
TemplateCreateRequest.call(this, model);
|
||||
this.id = model.Id;
|
||||
}
|
||||
|
||||
export function TemplateViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Title = data.title;
|
||||
|
|
|
@ -6,11 +6,7 @@ angular.module('portainer.app').factory('Templates', [
|
|||
API_ENDPOINT_TEMPLATES + '/:id',
|
||||
{},
|
||||
{
|
||||
create: { method: 'POST' },
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
update: { method: 'PUT', params: { id: '@id' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
query: { method: 'GET' },
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TemplateViewModel, TemplateCreateRequest, TemplateUpdateRequest } from '../../models/template';
|
||||
import { TemplateViewModel } from '../../models/template';
|
||||
|
||||
angular.module('portainer.app').factory('TemplateService', [
|
||||
'$q',
|
||||
|
@ -21,7 +21,7 @@ angular.module('portainer.app').factory('TemplateService', [
|
|||
dockerhub: DockerHubService.dockerhub(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
const templates = data.templates.map(function (item) {
|
||||
const templates = data.templates.templates.map(function (item) {
|
||||
const res = new TemplateViewModel(item);
|
||||
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(res.RegistryModel.Registry.URL, data.registries, data.dockerhub);
|
||||
registry.Image = res.RegistryModel.Image;
|
||||
|
@ -37,40 +37,6 @@ angular.module('portainer.app').factory('TemplateService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.template = function (id) {
|
||||
var deferred = $q.defer();
|
||||
let template;
|
||||
Templates.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
template = new TemplateViewModel(data);
|
||||
return RegistryService.retrievePorRegistryModelFromRepository(template.RegistryModel.Registry.URL);
|
||||
})
|
||||
.then((registry) => {
|
||||
registry.Image = template.RegistryModel.Image;
|
||||
template.RegistryModel = registry;
|
||||
deferred.resolve(template);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve template details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.delete = function (id) {
|
||||
return Templates.remove({ id: id }).$promise;
|
||||
};
|
||||
|
||||
service.create = function (model) {
|
||||
var payload = new TemplateCreateRequest(model);
|
||||
return Templates.create(payload).$promise;
|
||||
};
|
||||
|
||||
service.update = function (model) {
|
||||
var payload = new TemplateUpdateRequest(model);
|
||||
return Templates.update(payload).$promise;
|
||||
};
|
||||
|
||||
service.createTemplateConfiguration = function (template, containerName, network) {
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
import { TemplateDefaultModel } from '../../../models/template';
|
||||
|
||||
angular.module('portainer.app').controller('CreateTemplateController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
'TemplateService',
|
||||
'TemplateHelper',
|
||||
'NetworkService',
|
||||
'Notifications',
|
||||
function ($q, $scope, $state, TemplateService, TemplateHelper, NetworkService, Notifications) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.create = function () {
|
||||
var model = $scope.model;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
TemplateService.create(model)
|
||||
.then(function success() {
|
||||
Notifications.success('Template successfully created', model.Title);
|
||||
$state.go('portainer.templates');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create template');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$scope.model = new TemplateDefaultModel();
|
||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
$q.all({
|
||||
templates: TemplateService.templates(),
|
||||
networks: NetworkService.networks(provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', false, provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25),
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.categories = TemplateHelper.getUniqueCategories(data.templates);
|
||||
$scope.networks = data.networks;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve template details');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
|
@ -1,22 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Create template"></rd-header-title>
|
||||
<rd-header-content> <a ui-sref="portainer.templates">Templates</a> > Add template </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<template-form
|
||||
model="model"
|
||||
categories="categories"
|
||||
networks="networks"
|
||||
form-action="create"
|
||||
show-type-selector="true"
|
||||
form-action-label="Create the template"
|
||||
action-in-progress="state.actionInProgress"
|
||||
></template-form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,26 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Template details">
|
||||
<a data-toggle="tooltip" title-text="Refresh" ui-sref="portainer.templates.template({id: template.Id})" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content> <a ui-sref="portainer.templates">Templates</a> > {{ ::template.Title }} </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<template-form
|
||||
model="template"
|
||||
categories="categories"
|
||||
networks="networks"
|
||||
form-action="update"
|
||||
show-type-selector="false"
|
||||
form-action-label="Update the template"
|
||||
action-in-progress="state.actionInProgress"
|
||||
></template-form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,66 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.app').controller('TemplateController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'TemplateService',
|
||||
'TemplateHelper',
|
||||
'NetworkService',
|
||||
'Notifications',
|
||||
function ($q, $scope, $state, $transition$, TemplateService, TemplateHelper, NetworkService, Notifications) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.update = function () {
|
||||
var model = $scope.template;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
TemplateService.update(model)
|
||||
.then(function success() {
|
||||
Notifications.success('Template successfully updated', model.Title);
|
||||
$state.go('portainer.templates');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update template');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
var templateId = $transition$.params().id;
|
||||
$q.all({
|
||||
templates: TemplateService.templates(),
|
||||
template: TemplateService.template(templateId),
|
||||
networks: NetworkService.networks(provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', false, provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25),
|
||||
})
|
||||
.then(function success(data) {
|
||||
var template = data.template;
|
||||
if (template.Network) {
|
||||
template.Network = _.find(data.networks, function (o) {
|
||||
return o.Name === template.Network;
|
||||
});
|
||||
} else {
|
||||
template.Network = _.find(data.networks, function (o) {
|
||||
return o.Name === 'bridge';
|
||||
});
|
||||
}
|
||||
$scope.categories = TemplateHelper.getUniqueCategories(data.templates);
|
||||
$scope.template = data.template;
|
||||
$scope.networks = data.networks;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve template details');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
|
@ -366,10 +366,6 @@
|
|||
templates="templates"
|
||||
table-key="templates"
|
||||
select-action="selectTemplate"
|
||||
delete-action="deleteTemplate"
|
||||
show-add-action="state.templateManagement && isAdmin"
|
||||
show-update-action="state.templateManagement && isAdmin"
|
||||
show-delete-action="state.templateManagement && isAdmin"
|
||||
show-swarm-stacks="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER' && applicationState.endpoint.apiVersion >= 1.25"
|
||||
></template-list>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
|||
'SettingsService',
|
||||
'StackService',
|
||||
'EndpointProvider',
|
||||
'ModalService',
|
||||
function (
|
||||
$scope,
|
||||
$q,
|
||||
|
@ -39,15 +38,13 @@ angular.module('portainer.app').controller('TemplatesController', [
|
|||
FormValidator,
|
||||
SettingsService,
|
||||
StackService,
|
||||
EndpointProvider,
|
||||
ModalService
|
||||
EndpointProvider
|
||||
) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
formValidationError: '',
|
||||
actionInProgress: false,
|
||||
templateManagement: true,
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
|
@ -255,27 +252,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
|||
return TemplateService.createTemplateConfiguration(template, name, network);
|
||||
}
|
||||
|
||||
$scope.deleteTemplate = function (template) {
|
||||
ModalService.confirmDeletion('Do you want to delete this template?', function onConfirm(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
deleteTemplate(template);
|
||||
});
|
||||
};
|
||||
|
||||
function deleteTemplate(template) {
|
||||
TemplateService.delete(template.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Template successfully deleted');
|
||||
var idx = $scope.templates.indexOf(template);
|
||||
$scope.templates.splice(idx, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove template');
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
||||
|
@ -300,7 +276,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
|||
$scope.availableNetworks = networks;
|
||||
var settings = data.settings;
|
||||
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
||||
$scope.state.templateManagement = !settings.ExternalTemplates;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.templates = [];
|
||||
|
|
|
@ -154,7 +154,7 @@ function shell_run_container() {
|
|||
'docker rm -f portainer',
|
||||
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
|
||||
portainer_data +
|
||||
':/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json',
|
||||
':/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics',
|
||||
].join(';');
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue