mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(templates): remove template management features (#3719)
* feat(api): remove template management features * feat(templates): remove template management features
This commit is contained in:
parent
45f93882d0
commit
5563ff60fc
36 changed files with 26 additions and 965 deletions
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/portainer/portainer/api/bolt/tag"
|
"github.com/portainer/portainer/api/bolt/tag"
|
||||||
"github.com/portainer/portainer/api/bolt/team"
|
"github.com/portainer/portainer/api/bolt/team"
|
||||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
"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/user"
|
||||||
"github.com/portainer/portainer/api/bolt/version"
|
"github.com/portainer/portainer/api/bolt/version"
|
||||||
"github.com/portainer/portainer/api/bolt/webhook"
|
"github.com/portainer/portainer/api/bolt/webhook"
|
||||||
|
@ -58,7 +57,6 @@ type Store struct {
|
||||||
TagService *tag.Service
|
TagService *tag.Service
|
||||||
TeamMembershipService *teammembership.Service
|
TeamMembershipService *teammembership.Service
|
||||||
TeamService *team.Service
|
TeamService *team.Service
|
||||||
TemplateService *template.Service
|
|
||||||
TunnelServerService *tunnelserver.Service
|
TunnelServerService *tunnelserver.Service
|
||||||
UserService *user.Service
|
UserService *user.Service
|
||||||
VersionService *version.Service
|
VersionService *version.Service
|
||||||
|
@ -137,7 +135,6 @@ func (store *Store) MigrateData() error {
|
||||||
StackService: store.StackService,
|
StackService: store.StackService,
|
||||||
TagService: store.TagService,
|
TagService: store.TagService,
|
||||||
TeamMembershipService: store.TeamMembershipService,
|
TeamMembershipService: store.TeamMembershipService,
|
||||||
TemplateService: store.TemplateService,
|
|
||||||
UserService: store.UserService,
|
UserService: store.UserService,
|
||||||
VersionService: store.VersionService,
|
VersionService: store.VersionService,
|
||||||
FileService: store.fileService,
|
FileService: store.fileService,
|
||||||
|
@ -246,12 +243,6 @@ func (store *Store) initServices() error {
|
||||||
}
|
}
|
||||||
store.TeamService = teamService
|
store.TeamService = teamService
|
||||||
|
|
||||||
templateService, err := template.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.TemplateService = templateService
|
|
||||||
|
|
||||||
tunnelServerService, err := tunnelserver.NewService(store.db)
|
tunnelServerService, err := tunnelserver.NewService(store.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package migrator
|
package migrator
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *Migrator) updateSettingsToDBVersion15() error {
|
func (m *Migrator) updateSettingsToDBVersion15() error {
|
||||||
legacySettings, err := m.settingsService.Settings()
|
legacySettings, err := m.settingsService.Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,19 +11,6 @@ func (m *Migrator) updateSettingsToDBVersion15() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Migrator) updateTemplatesToVersion15() error {
|
func (m *Migrator) updateTemplatesToVersion15() error {
|
||||||
legacyTemplates, err := m.templateService.Templates()
|
// Removed with the entire template management layer, part of https://github.com/portainer/portainer/issues/3707
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/portainer/portainer/api/bolt/stack"
|
"github.com/portainer/portainer/api/bolt/stack"
|
||||||
"github.com/portainer/portainer/api/bolt/tag"
|
"github.com/portainer/portainer/api/bolt/tag"
|
||||||
"github.com/portainer/portainer/api/bolt/teammembership"
|
"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/user"
|
||||||
"github.com/portainer/portainer/api/bolt/version"
|
"github.com/portainer/portainer/api/bolt/version"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +36,6 @@ type (
|
||||||
stackService *stack.Service
|
stackService *stack.Service
|
||||||
tagService *tag.Service
|
tagService *tag.Service
|
||||||
teamMembershipService *teammembership.Service
|
teamMembershipService *teammembership.Service
|
||||||
templateService *template.Service
|
|
||||||
userService *user.Service
|
userService *user.Service
|
||||||
versionService *version.Service
|
versionService *version.Service
|
||||||
fileService portainer.FileService
|
fileService portainer.FileService
|
||||||
|
@ -59,7 +57,6 @@ type (
|
||||||
StackService *stack.Service
|
StackService *stack.Service
|
||||||
TagService *tag.Service
|
TagService *tag.Service
|
||||||
TeamMembershipService *teammembership.Service
|
TeamMembershipService *teammembership.Service
|
||||||
TemplateService *template.Service
|
|
||||||
UserService *user.Service
|
UserService *user.Service
|
||||||
VersionService *version.Service
|
VersionService *version.Service
|
||||||
FileService portainer.FileService
|
FileService portainer.FileService
|
||||||
|
@ -82,7 +79,6 @@ func NewMigrator(parameters *Parameters) *Migrator {
|
||||||
settingsService: parameters.SettingsService,
|
settingsService: parameters.SettingsService,
|
||||||
tagService: parameters.TagService,
|
tagService: parameters.TagService,
|
||||||
teamMembershipService: parameters.TeamMembershipService,
|
teamMembershipService: parameters.TeamMembershipService,
|
||||||
templateService: parameters.TemplateService,
|
|
||||||
stackService: parameters.StackService,
|
stackService: parameters.StackService,
|
||||||
userService: parameters.UserService,
|
userService: parameters.UserService,
|
||||||
versionService: parameters.VersionService,
|
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://")
|
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||||
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
||||||
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
|
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")
|
errInvalidSyncInterval = portainer.Error("Invalid synchronization interval")
|
||||||
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
||||||
errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints")
|
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')),
|
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(),
|
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(),
|
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()
|
kingpin.Parse()
|
||||||
|
@ -83,12 +81,7 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
return errEndpointExcludeExternal
|
return errEndpointExcludeExternal
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateTemplateFile(*flags.TemplateFile)
|
err := validateEndpointURL(*flags.EndpointURL)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateEndpointURL(*flags.EndpointURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,16 +166,6 @@ func validateExternalEndpoints(externalEndpoints string) error {
|
||||||
return nil
|
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 {
|
func validateSyncInterval(syncInterval string) error {
|
||||||
if syncInterval != defaultSyncInterval {
|
if syncInterval != defaultSyncInterval {
|
||||||
_, err := time.ParseDuration(syncInterval)
|
_, err := time.ParseDuration(syncInterval)
|
||||||
|
|
|
@ -21,5 +21,4 @@ const (
|
||||||
defaultSyncInterval = "60s"
|
defaultSyncInterval = "60s"
|
||||||
defaultSnapshot = "true"
|
defaultSnapshot = "true"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSnapshotInterval = "5m"
|
||||||
defaultTemplateFile = "/templates.json"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,5 +19,4 @@ const (
|
||||||
defaultSyncInterval = "60s"
|
defaultSyncInterval = "60s"
|
||||||
defaultSnapshot = "true"
|
defaultSnapshot = "true"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSnapshotInterval = "5m"
|
||||||
defaultTemplateFile = "/templates.json"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -276,6 +275,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
||||||
EnableHostManagementFeatures: false,
|
EnableHostManagementFeatures: false,
|
||||||
SnapshotInterval: *flags.SnapshotInterval,
|
SnapshotInterval: *flags.SnapshotInterval,
|
||||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||||
|
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flags.Templates != "" {
|
if *flags.Templates != "" {
|
||||||
|
@ -296,45 +296,6 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
||||||
return nil
|
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 {
|
func retrieveFirstEndpointFromDatabase(endpointService portainer.EndpointService) *portainer.Endpoint {
|
||||||
endpoints, err := endpointService.Endpoints()
|
endpoints, err := endpointService.Endpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -561,11 +522,6 @@ func main() {
|
||||||
|
|
||||||
composeStackManager := initComposeStackManager(*flags.Data, reverseTunnelService)
|
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)
|
err = initSettings(store.SettingsService, flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -674,7 +630,6 @@ func main() {
|
||||||
StackService: store.StackService,
|
StackService: store.StackService,
|
||||||
ScheduleService: store.ScheduleService,
|
ScheduleService: store.ScheduleService,
|
||||||
TagService: store.TagService,
|
TagService: store.TagService,
|
||||||
TemplateService: store.TemplateService,
|
|
||||||
WebhookService: store.WebhookService,
|
WebhookService: store.WebhookService,
|
||||||
SwarmStackManager: swarmStackManager,
|
SwarmStackManager: swarmStackManager,
|
||||||
ComposeStackManager: composeStackManager,
|
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/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 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
|
||||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
|
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 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.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
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}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
url := portainer.EdgeTemplatesURL
|
url := portainer.DefaultTemplatesURL
|
||||||
if settings.TemplatesURL != "" {
|
if settings.TemplatesURL != "" {
|
||||||
url = settings.TemplatesURL
|
url = settings.TemplatesURL
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ type publicSettingsResponse struct {
|
||||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
|
||||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +35,6 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
||||||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||||
ExternalTemplates: false,
|
|
||||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||||
settings.OAuthSettings.AuthorizationURI,
|
settings.OAuthSettings.AuthorizationURI,
|
||||||
settings.OAuthSettings.ClientID,
|
settings.OAuthSettings.ClientID,
|
||||||
|
@ -44,9 +42,5 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
||||||
settings.OAuthSettings.Scopes),
|
settings.OAuthSettings.Scopes),
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.TemplatesURL != "" {
|
|
||||||
publicSettings.ExternalTemplates = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.JSON(w, publicSettings)
|
return response.JSON(w, publicSettings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,9 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler represents an HTTP API handler for managing templates.
|
// Handler represents an HTTP API handler for managing templates.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
TemplateService portainer.TemplateService
|
|
||||||
SettingsService portainer.SettingsService
|
SettingsService portainer.SettingsService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,29 +23,5 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
|
|
||||||
h.Handle("/templates",
|
h.Handle("/templates",
|
||||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
|
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
|
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
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
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
|
// 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}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
var templates []portainer.Template
|
resp, err := http.Get(settings.TemplatesURL)
|
||||||
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)
|
|
||||||
if err != nil {
|
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 nil
|
||||||
return response.JSON(w, filteredTemplates)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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.
|
// 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).
|
// 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 {
|
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||||
|
|
|
@ -78,7 +78,6 @@ type Server struct {
|
||||||
TagService portainer.TagService
|
TagService portainer.TagService
|
||||||
TeamService portainer.TeamService
|
TeamService portainer.TeamService
|
||||||
TeamMembershipService portainer.TeamMembershipService
|
TeamMembershipService portainer.TeamMembershipService
|
||||||
TemplateService portainer.TemplateService
|
|
||||||
UserService portainer.UserService
|
UserService portainer.UserService
|
||||||
WebhookService portainer.WebhookService
|
WebhookService portainer.WebhookService
|
||||||
Handler *handler.Handler
|
Handler *handler.Handler
|
||||||
|
@ -282,7 +281,6 @@ func (server *Server) Start() error {
|
||||||
var supportHandler = support.NewHandler(requestBouncer)
|
var supportHandler = support.NewHandler(requestBouncer)
|
||||||
|
|
||||||
var templatesHandler = templates.NewHandler(requestBouncer)
|
var templatesHandler = templates.NewHandler(requestBouncer)
|
||||||
templatesHandler.TemplateService = server.TemplateService
|
|
||||||
templatesHandler.SettingsService = server.SettingsService
|
templatesHandler.SettingsService = server.SettingsService
|
||||||
|
|
||||||
var uploadHandler = upload.NewHandler(requestBouncer)
|
var uploadHandler = upload.NewHandler(requestBouncer)
|
||||||
|
|
|
@ -538,7 +538,8 @@ type (
|
||||||
AccessLevel ResourceAccessLevel `json:"AccessLevel"`
|
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 {
|
Template struct {
|
||||||
// Mandatory container/stack fields
|
// Mandatory container/stack fields
|
||||||
ID TemplateID `json:"Id"`
|
ID TemplateID `json:"Id"`
|
||||||
|
@ -553,7 +554,7 @@ type (
|
||||||
// Mandatory stack fields
|
// Mandatory stack fields
|
||||||
Repository TemplateRepository `json:"repository"`
|
Repository TemplateRepository `json:"repository"`
|
||||||
|
|
||||||
// Mandatory edge stack fields
|
// Mandatory Edge stack fields
|
||||||
StackFile string `json:"stackFile"`
|
StackFile string `json:"stackFile"`
|
||||||
|
|
||||||
// Optional stack/container fields
|
// Optional stack/container fields
|
||||||
|
@ -943,15 +944,6 @@ type (
|
||||||
DeleteTeamMembershipByTeamID(teamID TeamID) error
|
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 represents a service for managing data associated to the tunnel server
|
||||||
TunnelServerService interface {
|
TunnelServerService interface {
|
||||||
Info() (*TunnelServerInfo, error)
|
Info() (*TunnelServerInfo, error)
|
||||||
|
@ -1039,8 +1031,8 @@ const (
|
||||||
DefaultEdgeAgentCheckinIntervalInSeconds = 5
|
DefaultEdgeAgentCheckinIntervalInSeconds = 5
|
||||||
// LocalExtensionManifestFile represents the name of the local manifest file for extensions
|
// LocalExtensionManifestFile represents the name of the local manifest file for extensions
|
||||||
LocalExtensionManifestFile = "/extensions.json"
|
LocalExtensionManifestFile = "/extensions.json"
|
||||||
// EdgeTemplatesURL represents the URL used to retrieve Edge templates
|
// DefaultTemplatesURL represents the URL to the official templates supported by Portainer
|
||||||
EdgeTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-1.20.0.json"
|
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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(root);
|
||||||
$stateRegistryProvider.register(portainer);
|
$stateRegistryProvider.register(portainer);
|
||||||
$stateRegistryProvider.register(about);
|
$stateRegistryProvider.register(about);
|
||||||
|
@ -595,7 +573,5 @@ angular.module('portainer.app', []).config([
|
||||||
$stateRegistryProvider.register(teams);
|
$stateRegistryProvider.register(teams);
|
||||||
$stateRegistryProvider.register(team);
|
$stateRegistryProvider.register(team);
|
||||||
$stateRegistryProvider.register(templates);
|
$stateRegistryProvider.register(templates);
|
||||||
$stateRegistryProvider.register(template);
|
|
||||||
$stateRegistryProvider.register(templateCreation);
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -3,8 +3,5 @@ angular.module('portainer.app').component('templateItem', {
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '=',
|
model: '=',
|
||||||
onSelect: '<',
|
onSelect: '<',
|
||||||
onDelete: '<',
|
|
||||||
showUpdateAction: '<',
|
|
||||||
showDeleteAction: '<',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,15 +28,6 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<!-- !blocklist-item-line1 -->
|
<!-- !blocklist-item-line1 -->
|
||||||
<!-- blocklist-item-line2 -->
|
<!-- blocklist-item-line2 -->
|
||||||
|
|
|
@ -7,10 +7,6 @@ angular.module('portainer.app').component('templateList', {
|
||||||
templates: '<',
|
templates: '<',
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
selectAction: '<',
|
selectAction: '<',
|
||||||
deleteAction: '<',
|
|
||||||
showSwarmStacks: '<',
|
showSwarmStacks: '<',
|
||||||
showAddAction: '<',
|
|
||||||
showUpdateAction: '<',
|
|
||||||
showDeleteAction: '<',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,10 +49,7 @@
|
||||||
<template-item
|
<template-item
|
||||||
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
||||||
model="template"
|
model="template"
|
||||||
show-update-action="$ctrl.showUpdateAction"
|
|
||||||
show-delete-action="$ctrl.showDeleteAction"
|
|
||||||
on-select="($ctrl.selectAction)"
|
on-select="($ctrl.selectAction)"
|
||||||
on-delete="($ctrl.deleteAction)"
|
|
||||||
></template-item>
|
></template-item>
|
||||||
<div ng-if="!$ctrl.templates" class="text-center text-muted">
|
<div ng-if="!$ctrl.templates" class="text-center text-muted">
|
||||||
Loading...
|
Loading...
|
||||||
|
|
|
@ -9,7 +9,6 @@ export function SettingsViewModel(data) {
|
||||||
this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
|
this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
|
||||||
this.SnapshotInterval = data.SnapshotInterval;
|
this.SnapshotInterval = data.SnapshotInterval;
|
||||||
this.TemplatesURL = data.TemplatesURL;
|
this.TemplatesURL = data.TemplatesURL;
|
||||||
this.ExternalTemplates = data.ExternalTemplates;
|
|
||||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||||
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
||||||
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
||||||
|
@ -21,7 +20,6 @@ export function PublicSettingsViewModel(settings) {
|
||||||
this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
|
this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
|
||||||
this.AuthenticationMethod = settings.AuthenticationMethod;
|
this.AuthenticationMethod = settings.AuthenticationMethod;
|
||||||
this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
||||||
this.ExternalTemplates = settings.ExternalTemplates;
|
|
||||||
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
||||||
this.LogoURL = settings.LogoURL;
|
this.LogoURL = settings.LogoURL;
|
||||||
this.OAuthLoginURI = settings.OAuthLoginURI;
|
this.OAuthLoginURI = settings.OAuthLoginURI;
|
||||||
|
|
|
@ -1,58 +1,6 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
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) {
|
export function TemplateViewModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.Title = data.title;
|
this.Title = data.title;
|
||||||
|
|
|
@ -6,11 +6,7 @@ angular.module('portainer.app').factory('Templates', [
|
||||||
API_ENDPOINT_TEMPLATES + '/:id',
|
API_ENDPOINT_TEMPLATES + '/:id',
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
create: { method: 'POST' },
|
query: { method: 'GET' },
|
||||||
query: { method: 'GET', isArray: true },
|
|
||||||
get: { method: 'GET', params: { id: '@id' } },
|
|
||||||
update: { method: 'PUT', params: { id: '@id' } },
|
|
||||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TemplateViewModel, TemplateCreateRequest, TemplateUpdateRequest } from '../../models/template';
|
import { TemplateViewModel } from '../../models/template';
|
||||||
|
|
||||||
angular.module('portainer.app').factory('TemplateService', [
|
angular.module('portainer.app').factory('TemplateService', [
|
||||||
'$q',
|
'$q',
|
||||||
|
@ -21,7 +21,7 @@ angular.module('portainer.app').factory('TemplateService', [
|
||||||
dockerhub: DockerHubService.dockerhub(),
|
dockerhub: DockerHubService.dockerhub(),
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.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 res = new TemplateViewModel(item);
|
||||||
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(res.RegistryModel.Registry.URL, data.registries, data.dockerhub);
|
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(res.RegistryModel.Registry.URL, data.registries, data.dockerhub);
|
||||||
registry.Image = res.RegistryModel.Image;
|
registry.Image = res.RegistryModel.Image;
|
||||||
|
@ -37,40 +37,6 @@ angular.module('portainer.app').factory('TemplateService', [
|
||||||
return deferred.promise;
|
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) {
|
service.createTemplateConfiguration = function (template, containerName, network) {
|
||||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||||
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
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"
|
templates="templates"
|
||||||
table-key="templates"
|
table-key="templates"
|
||||||
select-action="selectTemplate"
|
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"
|
show-swarm-stacks="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER' && applicationState.endpoint.apiVersion >= 1.25"
|
||||||
></template-list>
|
></template-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,7 +20,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
||||||
'SettingsService',
|
'SettingsService',
|
||||||
'StackService',
|
'StackService',
|
||||||
'EndpointProvider',
|
'EndpointProvider',
|
||||||
'ModalService',
|
|
||||||
function (
|
function (
|
||||||
$scope,
|
$scope,
|
||||||
$q,
|
$q,
|
||||||
|
@ -39,15 +38,13 @@ angular.module('portainer.app').controller('TemplatesController', [
|
||||||
FormValidator,
|
FormValidator,
|
||||||
SettingsService,
|
SettingsService,
|
||||||
StackService,
|
StackService,
|
||||||
EndpointProvider,
|
EndpointProvider
|
||||||
ModalService
|
|
||||||
) {
|
) {
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
selectedTemplate: null,
|
selectedTemplate: null,
|
||||||
showAdvancedOptions: false,
|
showAdvancedOptions: false,
|
||||||
formValidationError: '',
|
formValidationError: '',
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
templateManagement: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
|
@ -255,27 +252,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
||||||
return TemplateService.createTemplateConfiguration(template, name, network);
|
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() {
|
function initView() {
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
|
|
||||||
|
@ -300,7 +276,6 @@ angular.module('portainer.app').controller('TemplatesController', [
|
||||||
$scope.availableNetworks = networks;
|
$scope.availableNetworks = networks;
|
||||||
var settings = data.settings;
|
var settings = data.settings;
|
||||||
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
||||||
$scope.state.templateManagement = !settings.ExternalTemplates;
|
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
$scope.templates = [];
|
$scope.templates = [];
|
||||||
|
|
|
@ -154,7 +154,7 @@ function shell_run_container() {
|
||||||
'docker rm -f portainer',
|
'docker rm -f portainer',
|
||||||
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
|
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
|
||||||
portainer_data +
|
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(';');
|
].join(';');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue