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

feat(kubernetes): introduce kubernetes support (#3987)

* feat(kubernetes): fix duplicate published mode

* feat(kubernetes): group port mappings by applications

* feat(kubernetes): updated UX

* feat(kubernetes): updated UX

* feat(kubernetes): new applications list view

* fix(kubernetes): applications - expand ports on row click

* refactor(kubernetes): applications - replace old view with new

* fix(kubernetes): disable access management for default resource pool

* feat(kubernetes): app creation - limit stacks suggestion to selected resource pool

* feat(kubernetes): do not allow access management on system resource pools

* refactor(kubernetes): refactor services

* create view node detail

* compute node status

* compute resource reservations

* resource reservation progress bar

* create applications node datatable

* fix(kubernetes): fix invalid method name

* feat(kubernetes): minor UI changes

* feat(kubernetes): update application inspect UI

* feat(kubernetes): add the ability to copy load balancer IP

* fix(kubernetes): minor fixes on applications view

* feat(kubernetes): set usage level info on progress bars

* fix(kubernetes): fix an issue with duplicate pagination controls

* fix(kubernetes): fix an issue with unexpandable items

* refacto(kubernetes): clean status and resource computation

* fix(kubernetes): remove a bad line

* feat(kubernetes): update application detail view

* feat(kubernetes): change few things on view

* refacto(kubernetes): Corrections relative to PR #13

* refacto(kubernetes): remove old functions

* feat(kubernetes): add application pod logs

* fix(kubernetes): PR #13

* feat(kubernetes): Enable quotas by default

* feat(kubernetes): allow non admin to have access to ressource pool list/detail view

* feat(kubernetes): UI changes

* fix(kubernetes): fix resource reservation computation in node view

* fix(kubernetes): pods are correctly filter by app name

* fix(kubernetes): nodeapplicationsdatatable is correctly reorder by cpu and memory

* fix(kubernetes): nodeapplications datatable is correctly reorder on reload

* feat(kubernetes): update podService

* refacto(kubernetes): rename nodeInspect as node

* refaceto(kubernetes): use colspan 6 instead of colspan 3

* refacto(kubernetes): use genericdatatablecontroller and make isadmin a binding

* refacto(kubernetes): remove not needed lines

* refacto(kubernetes) extract usageLevelInfo as html filter

* refacto(kubernetes): no line break for params

* refacto(kubernetes): change on node converter and filters

* refacto(kubernetes): remove bad indentations

* feat(kubernetes): add plain text informations about resources limits for non admibn user

* refacto(kubernetes): ES6 format

* refacto(kubernetes): format

* refacto(kubernetes): format

* refacto(kubernetes): add refresh callback for nodeapplicationsdatatable

* refacto(kubernetes): change if else structure

* refactor(kubernetes): files naming and format

* fix(kubernetes): remove checkbox and actions on resourcespools view for non admin

* feat(kubernetes): minor UI update

* fix(kubernetes): bind this on getPodsApplications to allow it to access $async

* fix(kubernetes): bind this on getEvents to allow it to access $async

* fix(kubernetes): format

* feat(kubernetes): minor UI update

* feat(kubernetes): add support for container console

* fix(kubernetes): fix a merge issue

* feat(kubernetes): update container console UI

* fix(api): fix typo

* feat(api): proxy pod websocket to agent

* fix(api): fix websocket pod proxy

* refactor(kubernetes): uniformize k8s merge comments

* refactor(kubernetes): update consoleController

* feat(kubernetes): prevent the removal of the default resource pool (#38)

* feat(kubernetes): show all applications running inside the resource pool (#35)

* add new datatable

* feat(kubernetes): add resource pool applications datatable to resource pool detail view

* refacto(kubernetes): factorise computeResourceReservation

* fix(kubernetes): colspan 6 to colspan 5

* fix(kubernetes): rename resourceReservationHelper into kubernetesResourceReservationHelper

* fix(kubernetes): add await to avoid double diggest cycles

* feat(kubernetes): add link to application name

* fix(kubernetes): change kubernetes-resource-pool-applications-datatable table key

* fix(kubernetes): change wording

* feat(kubernetes): add proper support for persisted folders (#36)

* feat(kubernetes): persistent volume mockups

* feat(kubernetes): persistent volume mockups

* feat(kubernetes): update persisted folders mockups

* feat(kubernetes): endpoint configure storage access policies

* fix(kubernetes): restrict advanced deployment to admin

* refactor(kubernetes): storageclass service / rest / model

* refactor(kubernetes): params/payload/converter pattern for deployments and daemonsets

* feat(kubernetes): statefulset management for applications

* fix(kubernets): associate application and pods

* feat(kubernetes): statefulset support for applications

* refactor(kubernetes): rebase on pportainer/k8s

* fix(kubernetes): app create - invalid targetPort on loadbalancer

* fix(kubernetes): internal services showed as loadbalancer

* fix(kubernetes): service ports creation / parsing

* fix(kubernetes): remove ports on headless services + ensure nodePort is used only for Cluster publishing

* fix(kubernetes): delete headless service on statefulset delete

* fix(kubernetes): statefulset replicas count display

* refactor(kubernetes): rebase on pportainer/k8s

* refactor(kubernetes): cleanup

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* fix(kubernetes): remove mockup routes

* feat(kubernetes): only display applications running on node/in resource pool when there are any

* feat(kubernetes): review resource reservations and leverage requests instead of limits (#40)

* fix(kubernetes): filter resource reservation by app in node view (#48)

* refactor(kubernetes): remove review comment

* chore(version): bump version number

* refactor(kubernetes): remove unused stacks view and components

* feat(kubernetes): update CPU slider step to 0.1 for resource pools (#60)

* feat(kubernetes): round up application CPU values (#61)

* feat(kubernetes): add information about application resource reservat… (#62)

* feat(kubernetes): add information about application resource reservations

* feat(kubernetes): apply kubernetesApplicationCPUValue to application CPU reservation

* refactor(kubernetes): services layer with models/converter/payloads (#64)

* refactor(kubernetes): services layer with models/converter/payloads

* refactor(kubernetes): file rename and comment update

* style(kubernetes): replace strings double quotes with simple quotes

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* fix(kubernetes): filter application by node in node detail view (#69)

* fix(kubernetes): filter applications by node

* fix(kubernetes): remove js error

* refactor(kubernetes): delete resource quota deletion process when deleting a resource pool (#68)

* feat(kubernetes): enforce valid resource reservations and clarify its… (#70)

* feat(kubernetes): enforce valid resource reservations and clarify its usage

* feat(kubernetes): update instance count input behavior

* feat(kubernetes): resource pools labels (#71)

* feat(kubernetes): resource pools labels

* fix(kubernetes): RP/RQ/LR owner label

* feat(kubernetes): confirmation popup on RP delete (#76)

* feat(kubernetes): application labels (#72)

* feat(kubernetes): application labels

* feat(kubernetes): display application owner in details when available

* style(kubernetes): revert StackName column labels

* fix(kubernetes): default displayed StackName

* feat(kubernetes): remove RQ query across cluster (#73)

* refactor(kubernetes): routes as components (#75)

* refactor(kubernetes): routes as components

* refactor(kubernetes): use component  lifecycle hook

* refactor(kubernetes): files naming consistency

* fix(kubernetes): fix invalid component name for cluster view

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): update portaineruser cluster role policy rules (#78)

* refactor(kubernetes): remove unused helper

* fix(kubernetes): fix invalid reload link in cluster view

* feat(kubernetes): add cluster resource reservation (#77)

* feat(kubernetes): add cluster resource reservation

* fix(kubernetes): filter resource reservation with applications

* fix(kubernetes): fix indent

* refacto(kubernetes): extract megabytes value calc as resourceReservationHelper method

* fix(kubernetes): remove unused import

* refacto(kubernetes): add resourcereservation model

* fix(kubernetes): add parenthesis on arrow functions parameters

* refacto(kubernetes): getpods in applicationService getAll

* fix(kubernetes): let to const

* fix(kubernetes): remove unused podservice

* fix(kubernetes): fix computeResourceReservation

* fix(kubernetes): app.pods to app.Pods everywhere and camelcase of this.ResourceReservation

* feat(kubernetes): configurations list view (#74)

* feat(kubernetes): add configuration list view

* feat(kubernetes): add configurations datatable

* feat(kubernetes): add item selection

* feat(kubernetes): allow to remove configuration

* feat(kubernetes): allow non admin user to see configurations

* fix(kubernetes): configurations view as component

* feat(kubernetes): remove stack property for secret and configurations

* fix(kubernetes): update import

* fix(kubernetes): remove secret delete payload

* fix(kubernetes): rename configuration model

* fix(kubernetes): remove configmap delete payload

* fix(Kubernetes): fix configuration getAsync

* fix(kubernetes): extract params as variables

* refacto(kubernetes): extract configurations used lines as helper

* fix(kubernetes): add verification of _.find return value

* fix(kubernetes): fix kubernetes configurations datatable callback

* refacto(Kubernetes): extract find before if

* fix(kubernetes): replace this by KubernetesConfigurationHelper in static method

* fix(Kubernetes): fix getASync

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* review(kubernetes): todo comments (#80)

* feat(kubernetes): minor UI update

* feat(kubernetes): round max cpu value in application creation

* feat(kubernetes): minor UI update

* fix(kubernetes): no-wrap resource reservation bar text (#89)

* docs(kubernetes): add review for formValues to resource conversion (#91)

* feat(kubernetes): configuration creation view (#82)

* feat(kubernetes): create configuration view

* feat(kubernetes): add advanced mode and create entry from file

* fix(kubernetes): fix validation issues

* fix(kubernetes): fix wording

* fix(kubernetes): replace data by stringdata in secret payloads

* fix(kubernetes): rename KubernetesConfigurationEntry to KubernetesConfigurationFormValuesDataEntry

* refacto(kubernetes): add isSimple to formValues and change configuration creation pattern

* fix(kubernetes): fix some bugs

* refacto(kubernetes): renaming

* fix(kubernetes): fix few bugs

* fix(kubernetes): fix few bugs

* review(kubernetes): refactor notices

Co-authored-by: xAt0mZ <baron_l@epitech.eu>

* feat(kubernetes): rename codeclimate file

* feat(kubernetes): re-enable codeclimate

* feat(project): update codeclimate configuration

* feat(project): update codeclimate configuration

* feat(project): update codeclimate configuration

* feat(kubernetes): minor UI update

* feat(project): update codeclimate

* feat(project): update codeclimate configuration

* feat(project): update codeclimate configuration

* feat(kubernetes): configuration details view (#93)

* feat(kubernetes): configuration details view

* fix(kubernetes): fix wording

* fix(kubernetes): fix update button

* fix(kubernetes): line indent

* refacto(kubernetes): remove conversion

* refacto(kubernetes): remove useless line

* refacto(kubernetes): remove useless lines

* fix(kubernetes): revert error handling

* fix(kubernetes): fix wording

* fix(kubernetes): revert line deletion

* refacto(kubernetes): change data mapping

* fix(kubernetes): create before delete

* fix(kubernetes): fix duplicate bug

* feat(kubernetes): configurations in application creation (#92)

* feat(kubernetes): application configuration mockups

* feat(kubernetes): update mockup

* feat(kubernetes): app create - dynamic view for configurations

* feat(kubernetes): app create - configuration support

* refactor(kubernetes): more generic configuration conversion function

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): automatically display first entry in configuration creation

* feat(kubernetes): minor UI update regarding applications and configurations

* feat(kubernetes): update Cluster icon in sidebar

* feat(kubernetes): volumes list view (#112)

* feat(kubernetes): add a feedback panel on main views (#111)

* feat(kubernetes): add a feedback panel on main views

* feat(kubernetes): add feedback panel to volumes view

* fix(kubernetes): isolated volumes showed as unused even when used (#116)

* feat(kubernetes): remove limit range from Portainer (#119)

* limits instead of requests (#121)

* feat(kubernetes): volume details (#117)

* feat(kubernetes): volume details

* fix(kubernetes): yaml not showed

* feat(kubernetes): expandable stacks list (#122)

* feat(kubernetes): expandable stacks list

* feat(kubernetes): minor UI update to stacks datatable

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): uibprogress font color (#129)

* feat(kubernetes): minor UI update to resource reservation component

* feat(kubernetes): automatically select a configuration

* refactor(kubernetes): remove comment

* feat(kubernetes): minor UI update

* feat(kubernetes): add resource links and uniformize view headers (#133)

* feat(kubernetes): prevent removal of system configurations (#128)

* feat(kubernetes): prevent removal of system configurations

* fix(kubernetes): KubernetesNamespaceHelper is not a function

* refacto(kubernetes): change prevent removal pattern

* fix(kubernetes): remove unused dependencies

* fix(kubernetes): fix configuration used label (#123)

* fix(kubernetes): fix used configurations

* fix(kubernetes): remove console log

* feat(kubernetes): rename configuration types (#127)

* refacto(kubernetes): fix wording and use configMap instead of Basic in the code

* feat(kubernetes): prevent the removal of system configuration

* fix(kubernetes): remove feat on bad branch

* fix(kubernetes): rename configuration types

* refacto(kubernetes): use a numeric enum and add a filter to display the text type

* refacto(kubernetes): fix wording and use configMap instead of Basic in the code

* feat(kubernetes): prevent the removal of system configuration

* fix(kubernetes): remove feat on bad branch

* fix(kubernetes): rename configuration types

* refacto(kubernetes): use a numeric enum and add a filter to display the text type

* fix(kubernetes): rename file and not use default in switch case

* feat(kubernetes): update advanced deployment UI/UX (#130)

* feat(kubernetes): update advanced deployment UI/UX

* feat(kubernetes): review HTML tags indentation

* feat(kubernetes): applications stacks delete (#135)

* fix(kubernetes): multinode resources reservations (#118)

* fix(kubernetes): filter pods by node

* fix(kubernetes): fix applications by node filter

* fix(kubernetes): filter pods by node

* Update app/kubernetes/views/cluster/node/nodeController.js

Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>

* feat(kubernetes): limit usage of pod console view (#136)

* feat(kubernetes): add yaml and events to configuration details (#126)

* feat(kubernetes): add yaml and events to configuration details

* fix(kubernetes): fix errors on secret details view

* fix(kubernetes): display only events related to configuration

* fix(kubernetes): fix applications by node filter

* fix(kubernetes): revert commit on bad branch

* refacto(kubernetes): refacto configmap get yaml function

* refacto(kubernetes): add yaml into converter

* feat(kubernetes): improve application details (#141)

* refactor(kubernetes): remove applications retrieval from volume service

* feat(kubernetes): improve application details view

* feat(kubernetes): update kompose binary version (#143)

* feat(kubernetes): update kubectl version (#144)

* refactor(kubernetes): rename portainer system namespace (#145)

* feat(kubernetes): add a loading view indicator (#140)

* feat(kubernetes): add an example of view loading indicator

* refactor(css): remove comment

* feat(kubernetes): updated loading pattern

* feat(kubernetes): add loading indicator for resource pool views

* feat(kubernetes): add loading indicator for deploy view

* feat(kubernetes): add loading view indicator to dashboard

* feat(kubernetes): add loading view indicator to configure view

* feat(kubernetes): add loading indicator to configuration views

* feat(kubernetes): add loading indicator to cluster views

* feat(kubernetes): rebase on k8s branch

* feat(kubernetes): update icon size

* refactor(kubernetes): update indentation and tag format

* feat(kubernetes): backend role validation for stack deployment (#147)

* feat(kubernetes): show applications when volume is used

* feat(kubernetes): set empty value when node is not set

* feat(kubernetes): update configuration UI/UX

* feat(kubernetes): update configuration UX

* fix(kubernetes): Invalid value for a configuration (#139)

* fix(kubernetes): Invalid value for a configuration

* fix(kubernetes): remove auto JSON convertion for configMap ; apply it for RPool Accesses only

* refactor(kubernetes): remove unneeded line

* fix(kubernetes): remove default JSON parsing on configMap API retrieval

Co-authored-by: xAt0mZ <baron_l@epitech.eu>

* feat(kubernetes): applications table in configuration details (#154)

* feat(kubernetes): Add the ability to filter system resources (#142)

* feat(kubernetes): hide system configurations

* feat(kubernetes): Add the ability to filter system resources

* feat(kubernetes): add the ability to hide system resources on volumes

* fix(kubernetes): fix few issue in volumesDatatableController

* fix(kubernetes): fix applications / ports / stacks labels

* feat(kubernetes): add volumes and configurations to dashboard (#152)

* feat(kubernetes): event warning indicator (#150)

* feat(kubernetes): event warning indicator for applications

* refactor(kubernetes): refactor events indicator logic

* feat(kubernetes): add event warning indicator to all resources

* feat(kubernetes): fix missing YAML panel for node (#157)

* feat(kubernetes): revised application details view (#159)

* feat(kubernetes): revised application details view

* refactor(kubernetes): remove comment

* feat(kubernetes): rebase on k8s

* refactor(kubernetes): remove extra line

* feat(kubernetes): update kubernetes beta feedback panel locations (#161)

* feat(kubernetes): stack logs (#160)

* feat(kubernetes): stack logs

* fix(kubernetes): ignore starting pods

* fix(kubernetes): colspan on expandable stack applications table

* feat(kubernetes): add an information message about system resources (#163)

* fix(kubernetes): fix empty panel being display in cluster view (#165)

* fix(kubernetes): Invalid CPU unit for node (#156)

* fix(kubernetes): Invalid CPU unit for node

* fix(kubernetes): Invalid CPU unit for node

* refacto(kubernetes): extract parseCPU function in helper

* refacto(kubernetes): rewrite parseCPU function

* feat(kubernetes): add the kube-node-lease namespace to system namespaces (#177)

* feat(kubernetes): tag system applications on node details view (#175)

* feat(kubernetes): tag system applications on node details view

* fix(kubernetes): remove system resources filter

* feat(kubernetes): review UI/UX around volume size unit (#178)

* feat(kubernetes): updates after review (#174)

* feat(kubernetes): update access user message

* feat(kubernetes): relocate resource pool to a specific form section

* feat(kubernetes): review responsiveness of port mappings

* feat(kubernetes): clarify table settings

* feat(kubernetes): add resource reservation summary message

* feat(kubernetes): review wording (#182)

* feat(kubernetes): application stack edit (#179)

* feat(kubernetes): update UI -- update action missing

* feat(kubernetes): application stack update

* feat(kubernetes): change services stacks

* feat(kubernetes): hide default-tokens + prevent remove (#183)

* feat(kubernetes): hide default-tokens + prevent remove

* feat(kubernetes): do not display unused label for system configurations

* fix(kubernetes): minor fix around showing system configurations

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): rebase on k8s branch (#180)

* fix(kubernetes): prevent the display of system resources in dashboard (#186)

* fix(kubernetes): prevent the display of system resources in dashboard

* fix(kubernetes): prevent the display of frontend filtered resource pools

* feat(kubernetes): support downward API for env vars in application details (#181)

* feat(kubernetes): support downward API for env vars in application details

* refactor(kubernetes): remove comment

* feat(kubernetes): minor UI update

* feat(kubernetes): remove Docker features (#189)

* chore(version): bump version number (#187)

* chore(version): bump version number

* feat(kubernetes): disable update notice

* feat(kubernetes): minor UI update

* feat(kubernetes): minor UI update

* feat(kubernetes): form validation (#170)

* feat(kubernetes): add published node port value check

* feat(kubernetes): add a dns compliant validation

* fix(kubernetes): fix port range validation

* feat(kubernetes): lot of form validation

* feat(kubernetes): add lot of form validation

* feat(kubernetes): persisted folders size validation

* feat(kubernetes): persisted folder path should be unique

* fix(kubernetes): fix createResourcePool button

* fix(kubernetes): change few things

* fix(kubernetes): fix slider memory

* fix(kubernetes): fix duplicates on dynamic field list

* fix(kubernetes): remove bad validation on keys

* feat(kubernetes): minor UI enhancements and validation updates

* feat(kubernetes): minor UI update

* fix(kubernetes):  revert on slider fix

* review(kubernetes): add future changes to do

* fix(kubernetes): add form validation on create application memory slider

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
Co-authored-by: xAt0mZ <baron_l@epitech.eu>

* feat(kubernetes): remove Docker related content

* feat(kubernetes): update build system to remove docker binary install

* fix(kubernetes): fix an issue with missing user settings

* feat(kubernetes): created column for apps and resource pools (#184)

* feat(kubernetes): created column for apps and resource pools

* feat(kubernetes): configurations and volumes owner

* feat(kubernetes): rename datatables columns

* fix(kubernetes): auto detect statefulset headless service name (#196)

* fix(applications): display used configurations (#198)

* feat(kubernetes): app details - display data access policy (#199)

* feat(kubernetes): app details - display data access policy

* feat(kubernetes): tooltip on data access info

* feat(kubernetes): move DAP tooltip to end of line

* feat(kubernetes): minor UI update

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* fix(kubernetes): fix an issue when updating the local endpoint (#204)

* fix(kubernetes): add unique key to configuration overriden key path field (#207)

* feat(kubernetes): tag applications as external (#221)

* feat(kubernetes): tag applications as external first approach

* feat(kubernetes): tag applications as external

* feat(kubernetes): Use ibytes as the default volume size unit sent to the Kubernetes API (#222)

* feat(kubernetes): Use ibytes as the default volume size unit sent to the Kubernetes API

* fix(kubernetes): only display b units in list and details views

* feat(kubernetes): add note to application details (#212)

* feat(kubernetes): add note to application details

* fix(kubernetes): remove eslintcache

* feat(kubernetes): update application note UI

* feat(kubernetes): add an update button to the note form when a note is already associated to an app

* feat(kubernetes): fix with UI changes

* fix(kubernetes): change few things

* fix(kubernetes): remove duplicate button

* fix(kubernetes): just use a ternary

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): fix data persistence display for isolated DAP (#223)

* feat(kubernetes): add a quick action to copy application name to clipboard (#225)

* feat(kubernetes): revert useless converter changes (#228)

* feat(kubernetes): edit application view (#200)

* feat(kubernetes): application to formValues conversion

* feat(kubernetes): extract applicationFormValues conversion as converter function

* feat(kubernetes): draft app patch

* feat(kubernetes): patch on all apps services + service service + pvc service

* feat(kubernetes): move name to labels and use UUID as kubernetes Name + patch recreate if necessary

* feat(kubernetes): move user app name to label and use UUID for Kubernetes Name field

* feat(kubernetes): kubernetes service patch mechanism

* feat(kubernetes): application edit

* feat(kubernetes): remove stack edit on app details

* feat(kubernetes): revert app name saving in label - now reuse kubernetes Name field

* feat(kubernetes): remove the ability to edit the DAP

* feat(kubernetes): cancel button on edit view

* feat(kubernetes): remove ability to add/remove persisted folders for SFS edition

* feat(kubernetes): minor UI update and action changes

* feat(kubernetes): minor UI update

* feat(kubernetes): remove ability to edit app volumes sizes + disable update button if no changes are made + codeclimate

* fix(kubernetes): resource reservation sliders in app edit

* fix(kubernetes): patch returned with 422 when trying to create nested objects

* fix(kubernetes): changing app deployment type wasn't working (delete failure)

* style(kubernetes): codeclimate

* fix(kubernetes): app edit - limits sliders max value

* feat(kubernetes): remove prefix on service name as we enforce DNS compliant app names

* fix(kubernetes): edit app formvalues replica based on target replica count and not total pods count

* fix(kubernetes): disable update for RWO on multi replica + delete service when changing app type

* fix(kubernetes): app details running / target pods display

* feat(kubernetes): add partial patch for app details view

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): disable edit capability for external and system apps (#233)

* feat(kubernetes): minor UI update

* fix(kubernetes): edit application issues (#235)

* feat(kubernetes): disable edition of load balancer if it's in pending state

* fix(kubernetes): now able to change from LB to other publishing types

* feat(kuberntes): modal on edit click to inform on potential service interruption

* feat(kubernetes): hide note when empty + add capability to collapse it

* fix(kubernetes): UI/API desync + app update button enabled in some cases where it shouldn't be

* fix(kubernetes): all apps are now using rolling updates with specific conditions

* style(kubernetes): code indent

* fix(kubernetes): disable sync process on endpoint init as current endpoint is not saved in client state

* fix(kubernetes): sliders refresh on app create + app details bad display for sfs running pods

* feat(kubernetes): minor UI update

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): bump up kubectl version to v1.18.0

* feat(kubernetes): when refreshing a view, remember currently opened tabs (#226)

* feat(kubernetes): When refreshing a view, remember currently opened tabs

* fix(kubernetes): only persist the current tab inside the actual view

* fix(kubernetes): not working with refresh in view header

* fix(kubernetes): skip error on 404 headless service retrieval if missconfigured in sfs (#242)

* refactor(kubernetes): use KubernetesResourcePoolService instead of KubernetesNamespaceService (#243)

* fix(kubernetes): create service before app to enforce port availability (#239)

* fix(kubernetes): external flag on application ports mappings datatable (#245)

* refactor(kubernetes): remove unused KubernetesResourcePoolHelper (#246)

* refactor(kubernetes): make all *service.getAllAsync functions consistent (#249)

* feat(kubernetes): Tag external applications in the application table of the resource pool details view (#251)

* feat(kubernetes): add ability to redeploy application (#240)

* feat(kubernetes): add ability to redeploy application

* feat(kubernetes): allow redeploy for external apps

* Revert "feat(kubernetes): allow redeploy for external apps"

This reverts commit 093375a7e93c1a07b845ebca1618da034a97fbcd.

* refactor(kubernetes): use KubernetesPodService instead of REST KubernetesPods (#247)

* feat(kubernetes): prevent configuration properties edition (#248)

* feat(kubernetes): prevent configuration properties edition

* feat(kubernetes): Relocate the Data/Actions to a separate panel

* feat(kubernetes): remove unused functions

* feat(kubernetes): minor UI update

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* refactor(kubernetes): Simplify the FileReader usage (#254)

* refactor(kubernetes): simplify FileReader usage

* refactor(kubernetes): Simplify FileReader usage

* refactor(kubernetes): rename e as event for readability

* feat(kubernetes): Tag system Configs in the Config details view (#257)

* refactor(kubernetes): Refactor the isFormValid function of multiple controllers (#253)

* refactor(kubernetes): refactor isFormValid functions in configurations

* refactor(kubernetes): refactor isformValid functions in create application

* refactor(kubernetes): remove duplicate lines

* refactor(kubernetes): remove commented line

* feat(kubernetes): Tag external volumes and configs (#250)

* feat(kubernetes): Tag external volumes and configs

* feat(kubernetes): remove .eslintcache

* feat(kubernetes): change few things

* feat(kubernetes): don't tag system configuration as external

* feat(kubernetes): minor UI update

* feat(kubernetes): extract inline css and clean all tags

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* fix(kubernetes): daemon set edit (#258)

* fix(kubernetes): persistent folder unit parsing

* fix(kubernetes): edit daemonset on RWO storage

* fix(kubernetes): external SFS had unlinked volumes (#264)

* feat(kubernetes): prevent to override two different configs on the same filesystem path (#259)

* feat(kubernetes): prevent to override two different configs on the same filesystem path

* feat(kubernetes): The validation should only be triggered across Configurations.

* feat(kubernetes): fix validations issues

* feat(kubernetes): fix form validation

* feat(kubernetes): fix few things

* refactor(kubernetes): Review the code mirror component update for configurations (#260)

* refactor(kubernetes): extract duplicate configuration code into a component

* refactor(kubernetes): fix form validation issues

* refactor(kubernetes): fix missing value

* refactor(kubernetes): remove useless await

* feat(kubernetes): Update the shared access policy configuration for Storage (#263)

* feat(kubernetes): Update the shared access policy configuration for Storage

* Update app/kubernetes/models/storage-class/models.js

* feat(kubernetes): remove ROX references and checks

Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: xAt0mZ <baron_l@epitech.eu>

* feat(kubernetes): provide the remove/restore UX for environment variables when editing an application (#261)

* feat(kubernetes): Provide the remove/restore UX for environment variables when editing an application

* feat(kubernetes): fix ui issue

* feat(kubernetes): change few things

* fix(kubernetes): Invalid display for exposed ports in accessing the application section (#267)

* feat(kubernetes): application rollback (#269)

* feat(kubernetes): retrieve all versions of a deployment

* feat(kubernetes): application history for all types

* feat(kubernetes): deployment rollback

* feat(kubernetes): daemonset / statefulset rollback

* feat(kubernetes): remove the revision selector and rollback on previous version everytime

* feat(kubernetes): minor UI changes

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): reservations should be computed based on requests instead of limits (#268)

* feat(kubernetes): Reservations should be computed based on requests instead of limits

* feat(kubernetes): use requests instead of limits in application details

* feat(kubernetes): removes unused limits

* feat(kubernetes): Not so useless

* feat(kubernetes): use service selectors to bind apps and services (#270)

* feat(kubernetes): use service selectors to bind apps and services

* Update app/kubernetes/services/statefulSetService.js

* style(kubernetes): remove comment block

Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>

* chore(version): bump version number

* feat(kubernetes): update feedback panel text

* chore(app): add prettier to k8s

* style(app): apply prettier to k8s codebase

* fix(kubernetes): Cannot read property 'port' of undefined (#272)

* fix(kubernetes): Cannot read property 'port' of undefined

* fix(kubernetes): concat app ports outside publishedports loop

* fix(application): fix broken display of the persistence layer (#274)

* chore(kubernetes): fix conflicts

* chore(kubernetes): fix issues related to conflict resolution

* refactor(kubernetes): refactor code related to conflict resolution

* fix(kubernetes): fix a minor issue with assets import

* chore(app): update yarn.lock

* fix(application): ports mapping are now correctly detected (#300)

* fix(build-system): fix missing docker binary download step

* feat(kubernetes): application auto scaling details (#301)

* feat(kubernetes): application auto scaling details

* feat(kubernetes): minor UI update

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>

* feat(kubernetes): Introduce a "used by" column in the volume list view (#303)

Co-authored-by: xAt0mZ <baron_l@epitech.eu>
Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com>
Co-authored-by: xAt0mZ <xAt0mZ@users.noreply.github.com>
This commit is contained in:
Anthony Lapenna 2020-07-06 11:21:03 +12:00 committed by GitHub
parent 24528ecea8
commit af6bea5acc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
361 changed files with 20794 additions and 2349 deletions

View file

@ -8,7 +8,7 @@ async function initAuthentication(authManager, Authentication, $rootScope, $stat
// to have more controls on which URL should trigger the unauthenticated state.
$rootScope.$on('unauthenticated', function (event, data) {
if (!_.includes(data.config.url, '/v2/') && !_.includes(data.config.url, '/api/v4/')) {
$state.go('portainer.auth', { error: 'Your session has expired' });
$state.go('portainer.logout', { error: 'Your session has expired' });
}
});
@ -106,8 +106,7 @@ angular.module('portainer.app', []).config([
name: 'portainer.auth',
url: '/auth',
params: {
logout: false,
error: '',
reload: false,
},
views: {
'content@': {
@ -118,6 +117,22 @@ angular.module('portainer.app', []).config([
'sidebar@': {},
},
};
const logout = {
name: 'portainer.logout',
url: '/logout',
params: {
error: '',
performApiLogout: false,
},
views: {
'content@': {
templateUrl: './views/logout/logout.html',
controller: 'LogoutController',
controllerAs: 'ctrl',
},
'sidebar@': {},
},
};
var endpoints = {
name: 'portainer.endpoints',
@ -141,6 +156,18 @@ angular.module('portainer.app', []).config([
},
};
const endpointKubernetesConfiguration = {
name: 'portainer.endpoints.endpoint.kubernetesConfig',
url: '/configure',
views: {
'content@': {
templateUrl: '../kubernetes/views/configure/configure.html',
controller: 'KubernetesConfigureController',
controllerAs: 'ctrl',
},
},
};
var endpointCreation = {
name: 'portainer.endpoints.new',
url: '/new',
@ -235,6 +262,7 @@ angular.module('portainer.app', []).config([
'content@': {
templateUrl: './views/init/endpoint/initEndpoint.html',
controller: 'InitEndpointController',
controllerAs: 'ctrl',
},
},
};
@ -491,10 +519,12 @@ angular.module('portainer.app', []).config([
$stateRegistryProvider.register(about);
$stateRegistryProvider.register(account);
$stateRegistryProvider.register(authentication);
$stateRegistryProvider.register(logout);
$stateRegistryProvider.register(endpoints);
$stateRegistryProvider.register(endpoint);
$stateRegistryProvider.register(endpointAccess);
$stateRegistryProvider.register(endpointCreation);
$stateRegistryProvider.register(endpointKubernetesConfiguration);
$stateRegistryProvider.register(groups);
$stateRegistryProvider.register(group);
$stateRegistryProvider.register(groupAccess);

View file

@ -69,12 +69,14 @@
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-disabled="item.Inherited" ng-click="$ctrl.selectItem(item, $event)" />
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-disabled="$ctrl.disableRemove(item)" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
{{ item.Name }}
<span ng-if="item.Inherited" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">inherited</code></span>
<span ng-if="item.Override" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">override</code></span>
<span ng-if="$ctrl.inheritFrom && item.Inherited" class="text-muted small" style="margin-left: 2px;"
><code style="font-size: 85% !important;">inherited</code></span
>
<span ng-if="$ctrl.inheritFrom && item.Override" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">override</code></span>
</td>
<td>{{ item.Type }}</td>
<td ng-if="$ctrl.rbacEnabled">

View file

@ -6,7 +6,7 @@ angular.module('portainer.app').controller('AccessDatatableController', [
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
this.disableRemove = function (item) {
return item.Inherited;
return item.Inherited && this.inheritFrom;
};
this.allowSelection = function (item) {

View file

@ -1,6 +1,6 @@
import _ from 'lodash-es';
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
import { ResourceControlTypeString as RCTS, ResourceControlTypeInt as RCTI } from 'Portainer/models/resourceControl/resourceControlTypes';
import { ResourceControlTypeInt as RCTI, ResourceControlTypeString as RCTS } from 'Portainer/models/resourceControl/resourceControlTypes';
import { AccessControlPanelData } from './porAccessControlPanelModel';
angular.module('portainer.app').controller('porAccessControlPanelController', [

View file

@ -27,7 +27,6 @@
</span>
</div>
</div>
<div class="form-group" ng-if="ctrl.entityType !== 'registry'">
<label class="col-sm-3 col-lg-2 control-label text-left">
Role
@ -39,7 +38,6 @@
</span>
</div>
</div>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">

View file

@ -53,27 +53,14 @@ class PorAccessManagementController {
}
async $onInit() {
const entity = this.accessControlledEntity;
if (!entity) {
this.Notifications.error('Failure', 'Unable to retrieve accesses');
return;
}
if (!entity.UserAccessPolicies) {
entity.UserAccessPolicies = {};
}
if (!entity.TeamAccessPolicies) {
entity.TeamAccessPolicies = {};
}
const parent = this.inheritFrom;
if (parent && !parent.UserAccessPolicies) {
parent.UserAccessPolicies = {};
}
if (parent && !parent.TeamAccessPolicies) {
parent.TeamAccessPolicies = {};
}
this.roles = [];
this.rbacEnabled = false;
try {
const entity = this.accessControlledEntity;
const parent = this.inheritFrom;
// TODO: refactor
// extract this code and locate it in AccessService.accesses() function
// see resourcePoolAccessController for another usage of AccessService.accesses()
// which needs RBAC support
this.roles = [];
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
if (this.rbacEnabled) {
this.roles = await this.RoleService.roles();
@ -81,13 +68,7 @@ class PorAccessManagementController {
selectedRole: this.roles[0],
};
}
const data = await this.AccessService.accesses(
entity.UserAccessPolicies,
entity.TeamAccessPolicies,
parent ? parent.UserAccessPolicies : {},
parent ? parent.TeamAccessPolicies : {},
this.roles
);
const data = await this.AccessService.accesses(entity, parent, this.roles);
this.availableUsersAndTeams = _.orderBy(data.availableUsersAndTeams, 'Name', 'asc');
this.authorizedUsersAndTeams = data.authorizedUsersAndTeams;
} catch (err) {

View file

@ -6,6 +6,7 @@ function isBetween(value, a, b) {
return (value >= a && value <= b) || (value >= b && value <= a);
}
// TODO: review - refactor to use a class that can be extended
angular.module('portainer.app').controller('GenericDatatableController', [
'$interval',
'PaginationService',

View file

@ -1,8 +1,14 @@
<div class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
<div class="blocklist-item-box">
<span ng-class="['blocklist-item-logo', 'endpoint-item', { azure: $ctrl.model.Type === 3 }]">
<i ng-if="$ctrl.model.Type !== 4" ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
<i
ng-if="$ctrl.model.Type !== 4 && $ctrl.model.Type !== 5 && $ctrl.model.Type !== 6 && $ctrl.model.Type !== 7"
ng-class="$ctrl.model.Type | endpointtypeicon"
class="fa-4x blue-icon"
aria-hidden="true"
></i>
<img ng-if="$ctrl.model.Type === 4" src="~@/assets/images/edge_endpoint.png" />
<img ng-if="$ctrl.model.Type === 5 || $ctrl.model.Type === 6 || $ctrl.model.Type === 7" src="~@/assets/images/kubernetes_endpoint.png" />
</span>
<span class="col-sm-12">
@ -22,6 +28,9 @@
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
{{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Kubernetes.Snapshots[0]">
{{ $ctrl.model.Kubernetes.Snapshots[0].Time | getisodatefromtimestamp }}
</span>
</span>
</span>
<span>
@ -69,12 +78,36 @@
</span>
</div>
<div class="blocklist-item-line endpoint-item" ng-if="!$ctrl.model.Snapshots[0]">
<div class="blocklist-item-line endpoint-item" ng-if="!$ctrl.model.Snapshots[0] && $ctrl.model.Type !== 5 && $ctrl.model.Type !== 6 && $ctrl.model.Type !== 7">
<span class="blocklist-item-desc">
No snapshot available
</span>
</div>
<div class="blocklist-item-line endpoint-item" ng-if="$ctrl.model.Kubernetes.Snapshots[0] && ($ctrl.model.Type === 5 || $ctrl.model.Type === 6 || $ctrl.model.Type === 7)">
<span class="blocklist-item-desc">
<span>
<span style="padding: 0 7px 0 0;"> <i class="fa fa-microchip space-right" aria-hidden="true"></i>{{ $ctrl.model.Kubernetes.Snapshots[0].TotalCPU }} CPU </span>
<span style="padding: 0 7px 0 7px;">
<i class="fa fa-memory space-right" aria-hidden="true"></i>{{ $ctrl.model.Kubernetes.Snapshots[0].TotalMemory | humansize }} RAM
</span>
</span>
</span>
<span class="small text-muted">
Kubernetes {{ $ctrl.model.Kubernetes.Snapshots[0].KubernetesVersion }}
<span style="padding: 0 0 0 7px;">
<i class="fa fa-hdd space-left space-right" aria-hidden="true"></i>
{{ $ctrl.model.Kubernetes.Snapshots[0].NodeCount }} {{ $ctrl.model.Kubernetes.Snapshots[0].NodeCount === 1 ? 'node' : 'nodes' }}
</span>
</span>
</div>
<div class="blocklist-item-line endpoint-item" ng-if="!$ctrl.model.Kubernetes.Snapshots[0] && ($ctrl.model.Type === 5 || $ctrl.model.Type === 6 || $ctrl.model.Type === 7)">
<span class="blocklist-item-desc">
-
</span>
</div>
<div class="blocklist-item-line endpoint-item">
<span class="small text-muted">
<span ng-if="$ctrl.model.Type === 1">

View file

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

View file

@ -1,21 +1,45 @@
angular.module('portainer.app').controller('SliderController', function () {
var ctrl = this;
// TODO: k8s merge - TEST WITH EXISTING SLIDERS !
// Not sure if this is not breaking existing sliders on docker views
// Or sliders with onChange call (docker service update view)
import angular from 'angular';
ctrl.options = {
floor: ctrl.floor,
ceil: ctrl.ceil,
step: ctrl.step,
precision: ctrl.precision,
showSelectionBar: true,
enforceStep: false,
translate: function (value, sliderId, label) {
if ((label === 'floor' && ctrl.floor === 0) || value === 0) {
return 'unlimited';
}
return value;
},
onChange: function () {
ctrl.onChange();
},
};
});
class SliderController {
/* @ngInject */
constructor($scope) {
this.$scope = $scope;
this.buildOptions = this.buildOptions.bind(this);
this.translate = this.translate.bind(this);
}
$onChanges() {
this.buildOptions();
}
translate(value, sliderId, label) {
if ((label === 'floor' && this.floor === 0) || value === 0) {
return 'unlimited';
}
return value;
}
buildOptions() {
this.options = {
floor: this.floor,
ceil: this.ceil,
step: this.step,
precision: this.precision,
showSelectionBar: true,
enforceStep: false,
translate: this.translate,
onChange: () => this.onChange(),
};
}
$onInit() {
this.buildOptions();
}
}
export default SliderController;
angular.module('portainer.app').controller('SliderController', SliderController);

View file

@ -4,9 +4,12 @@ angular.module('portainer.app').directive('portainerTooltip', [
scope: {
message: '@',
position: '@',
customStyle: '<?',
},
template:
'<span class="interactive" tooltip-append-to-body="true" tooltip-placement="{{position}}" tooltip-class="portainer-tooltip" uib-tooltip="{{message}}"><i class="fa fa-question-circle tooltip-icon" aria-hidden="true"></i></span>',
template: `
<span class="interactive" tooltip-append-to-body="true" tooltip-placement="{{position}}" tooltip-class="portainer-tooltip" uib-tooltip="{{message}}">
<i class="" ng-class="['fa fa-question-circle blue-icon', {'tooltip-icon': !customStyle}]" ng-style="customStyle" aria-hidden="true"></i>
</span>`,
restrict: 'E',
};
return directive;

6
app/portainer/error.js Normal file
View file

@ -0,0 +1,6 @@
export default class PortainerError {
constructor(msg, err) {
this.msg = msg;
this.err = err;
}
}

View file

@ -126,11 +126,13 @@ angular
return function (type) {
if (type === 1) {
return 'Docker';
} else if (type === 2) {
} else if (type === 2 || type === 6) {
return 'Agent';
} else if (type === 3) {
return 'Azure ACI';
} else if (type === 4) {
} else if (type === 5) {
return 'Kubernetes';
} else if (type === 4 || type === 7) {
return 'Edge Agent';
}
return '';
@ -143,6 +145,8 @@ angular
return 'fab fa-microsoft';
} else if (type === 4) {
return 'fa fa-cloud';
} else if (type === 5 || type === 6 || type === 7) {
return 'fas fa-dharmachakra';
}
return 'fab fa-docker';
};

View file

@ -0,0 +1,41 @@
import { PortainerEndpointConnectionTypes } from 'Portainer/models/endpoint/models';
export class PortainerEndpointInitFormValues {
constructor() {
this.ConnectionType = PortainerEndpointConnectionTypes.KUBERNETES_LOCAL;
this.Name = '';
this.URL = '';
this.TLS = false;
this.TLSSkipVerify = false;
this.TLSSKipClientVerify = false;
this.TLSCACert = null;
this.TLSCert = null;
this.TLSKey = null;
this.AzureApplicationId = '';
this.AzureTenantId = '';
this.AzureAuthenticationKey = '';
}
}
class PortainerEndpointInitFormValueEndpointSection {
constructor(value, title, classes, description) {
this.Id = value;
this.Value = value;
this.Title = title;
this.Classes = classes;
this.Description = description;
}
}
export const PortainerEndpointInitFormValueEndpointSections = Object.freeze([
new PortainerEndpointInitFormValueEndpointSection(PortainerEndpointConnectionTypes.DOCKER_LOCAL, 'Docker', 'fab fa-docker', 'Manage the local Docker environment'),
new PortainerEndpointInitFormValueEndpointSection(
PortainerEndpointConnectionTypes.KUBERNETES_LOCAL,
'Kubernetes',
'fas fa-dharmachakra',
'Manage the local Kubernetes environment'
),
new PortainerEndpointInitFormValueEndpointSection(PortainerEndpointConnectionTypes.REMOTE, 'Remote', 'fab fa-docker', 'Manage a remote Docker environment'),
new PortainerEndpointInitFormValueEndpointSection(PortainerEndpointConnectionTypes.AGENT, 'Agent', 'fa fa-bolt', 'Connect to a Portainer agent'),
new PortainerEndpointInitFormValueEndpointSection(PortainerEndpointConnectionTypes.AZURE, 'Azure', 'fab fa-microsoft', 'Connect to Microsoft Azure ACI'),
]);

View file

@ -0,0 +1,28 @@
/**
* JS reference of portainer.go#EndpointType iota
*/
export const PortainerEndpointTypes = Object.freeze({
// DockerEnvironment represents an endpoint connected to a Docker environment
DockerEnvironment: 1,
// AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment
AgentOnDockerEnvironment: 2,
// AzureEnvironment represents an endpoint connected to an Azure environment
AzureEnvironment: 3,
// EdgeAgentOnDockerEnvironment represents an endpoint connected to an Edge agent deployed on a Docker environment
EdgeAgentOnDockerEnvironment: 4,
// KubernetesLocalEnvironment represents an endpoint connected to a local Kubernetes environment
KubernetesLocalEnvironment: 5,
// AgentOnKubernetesEnvironment represents an endpoint connected to a Portainer agent deployed on a Kubernetes environment
AgentOnKubernetesEnvironment: 6,
// EdgeAgentOnKubernetesEnvironment represents an endpoint connected to an Edge agent deployed on a Kubernetes environment
EdgeAgentOnKubernetesEnvironment: 7,
});
export const PortainerEndpointConnectionTypes = Object.freeze({
DOCKER_LOCAL: 1,
KUBERNETES_LOCAL: 2,
REMOTE: 3,
AZURE: 4,
AGENT: 5,
EDGE: 6,
});

View file

@ -4,13 +4,11 @@ angular.module('portainer.app').factory('Auth', [
function AuthFactory($resource, API_ENDPOINT_AUTH) {
'use strict';
return $resource(
API_ENDPOINT_AUTH,
API_ENDPOINT_AUTH + '/:action',
{},
{
login: {
method: 'POST',
ignoreLoadingBar: true,
},
login: { method: 'POST', ignoreLoadingBar: true },
logout: { method: 'POST', params: { action: 'logout' }, ignoreLoadingBar: true },
}
);
},

View file

@ -0,0 +1,34 @@
import _ from 'lodash-es';
/**
*
* @param {any[]} promises
*/
export default async function $allSettled(promises) {
const res = {
fulfilled: [],
rejected: [],
};
const data = await Promise.allSettled(promises);
res.fulfilled = _.reduce(
data,
(acc, item) => {
if (item.status === 'fulfilled') {
acc.push(item.value);
}
return acc;
},
[]
);
res.rejected = _.reduce(
data,
(acc, item) => {
if (item.status === 'rejected') {
acc.push(item.reason);
}
return acc;
},
[]
);
return res;
}

View file

@ -4,9 +4,10 @@ import { TeamAccessViewModel } from '../../models/access';
angular.module('portainer.app').factory('AccessService', [
'$q',
'$async',
'UserService',
'TeamService',
function AccessServiceFactory($q, UserService, TeamService) {
function AccessServiceFactory($q, $async, UserService, TeamService) {
'use strict';
var service = {};
@ -15,6 +16,7 @@ angular.module('portainer.app').factory('AccessService', [
const role = _.find(roles, (role) => role.Id === roleId);
return role ? role : { Id: 0, Name: '-' };
}
return { Id: 0, Name: '-' };
}
function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies, roles) {
@ -50,7 +52,7 @@ angular.module('portainer.app').factory('AccessService', [
};
}
service.accesses = function (authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies, roles) {
function getAccesses(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies, roles) {
var deferred = $q.defer();
$q.all({
@ -80,7 +82,36 @@ angular.module('portainer.app').factory('AccessService', [
});
return deferred.promise;
};
}
async function accessesAsync(entity, parent, roles) {
try {
if (!entity) {
throw { msg: 'Unable to retrieve accesses' };
}
if (!entity.UserAccessPolicies) {
entity.UserAccessPolicies = {};
}
if (!entity.TeamAccessPolicies) {
entity.TeamAccessPolicies = {};
}
if (parent && !parent.UserAccessPolicies) {
parent.UserAccessPolicies = {};
}
if (parent && !parent.TeamAccessPolicies) {
parent.TeamAccessPolicies = {};
}
return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {}, roles);
} catch (err) {
throw err;
}
}
function accesses(entity, parent, roles) {
return $async(accessesAsync, entity, parent, roles);
}
service.accesses = accesses;
service.generateAccessPolicies = function (userAccessPolicies, teamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId) {
const newUserPolicies = _.clone(userAccessPolicies);

View file

@ -1,3 +1,5 @@
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
angular.module('portainer.app').factory('EndpointService', [
'$q',
'Endpoints',
@ -57,7 +59,7 @@ angular.module('portainer.app').factory('EndpointService', [
service.createLocalEndpoint = function () {
var deferred = $q.defer();
FileUploadService.createEndpoint('local', 1, '', '', 1, [], false)
FileUploadService.createEndpoint('local', PortainerEndpointTypes.DockerEnvironment, '', '', 1, [], false)
.then(function success(response) {
deferred.resolve(response.data);
})
@ -86,7 +88,11 @@ angular.module('portainer.app').factory('EndpointService', [
var deferred = $q.defer();
var endpointURL = URL;
if (type !== 4) {
if (
type !== PortainerEndpointTypes.EdgeAgentOnDockerEnvironment &&
type !== PortainerEndpointTypes.AgentOnKubernetesEnvironment &&
type !== PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
) {
endpointURL = 'tcp://' + URL;
}
@ -115,6 +121,20 @@ angular.module('portainer.app').factory('EndpointService', [
return deferred.promise;
};
service.createLocalKubernetesEndpoint = function () {
var deferred = $q.defer();
FileUploadService.createEndpoint('local', 5, '', '', 1, [], true, true, true)
.then(function success(response) {
deferred.resolve(response.data);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to create endpoint', err: err });
});
return deferred.promise;
};
service.createAzureEndpoint = function (name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
var deferred = $q.defer();

View file

@ -1,7 +1,7 @@
import _ from 'lodash-es';
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
import { RegistryViewModel, RegistryCreateRequest } from '../../models/registry';
import { RegistryCreateRequest, RegistryViewModel } from '../../models/registry';
angular.module('portainer.app').factory('RegistryService', [
'$q',

View file

@ -1,17 +1,17 @@
import _ from 'lodash-es';
import { StackViewModel, ExternalStackViewModel } from '../../models/stack';
import { ExternalStackViewModel, StackViewModel } from '../../models/stack';
angular.module('portainer.app').factory('StackService', [
'$q',
'$async',
'Stack',
'ResourceControlService',
'FileUploadService',
'StackHelper',
'ServiceService',
'ContainerService',
'SwarmService',
'EndpointProvider',
function StackServiceFactory($q, Stack, ResourceControlService, FileUploadService, StackHelper, ServiceService, ContainerService, SwarmService, EndpointProvider) {
function StackServiceFactory($q, $async, Stack, FileUploadService, StackHelper, ServiceService, ContainerService, SwarmService, EndpointProvider) {
'use strict';
var service = {};
@ -252,7 +252,6 @@ angular.module('portainer.app').factory('StackService', [
return deferred.promise;
};
service.createComposeStackFromFileContent = function (name, stackFileContent, env, endpointId) {
var payload = {
Name: name,
@ -333,6 +332,23 @@ angular.module('portainer.app').factory('StackService', [
return action(name, stackFileContent, env, endpointId);
};
async function kubernetesDeployAsync(endpointId, namespace, content, compose) {
try {
const payload = {
StackFileContent: content,
ComposeFormat: compose,
Namespace: namespace,
};
await Stack.create({ method: 'undefined', type: 3, endpointId: endpointId }, payload).$promise;
} catch (err) {
throw { err: err };
}
}
service.kubernetesDeploy = function (endpointId, namespace, content, compose) {
return $async(kubernetesDeployAsync, endpointId, namespace, content, compose);
};
return service;
},
]);

View file

@ -2,8 +2,9 @@ import { TagViewModel } from '../../models/tag';
angular.module('portainer.app').factory('TagService', [
'$q',
'$async',
'Tags',
function TagServiceFactory($q, Tags) {
function TagServiceFactory($q, $async, Tags) {
'use strict';
var service = {};
@ -37,7 +38,7 @@ angular.module('portainer.app').factory('TagService', [
return deferred.promise;
};
service.createTag = async function (name) {
async function createTagAsync(name) {
var payload = {
Name: name,
};
@ -47,7 +48,12 @@ angular.module('portainer.app').factory('TagService', [
} catch (err) {
throw { msg: 'Unable to create tag', err };
}
};
}
function createTag(name) {
return $async(createTagAsync, name);
}
service.createTag = createTag;
service.deleteTag = function (id) {
return Tags.remove({ id: id }).$promise;

View file

@ -34,13 +34,21 @@ angular.module('portainer.app').factory('Authentication', [
}
}
function logout() {
async function logoutAsync(performApiLogout) {
if (performApiLogout) {
await Auth.logout().$promise;
}
StateManager.clean();
EndpointProvider.clean();
LocalStorage.clean();
LocalStorage.storeLoginStateUUID('');
}
function logout(performApiLogout) {
return $async(logoutAsync, performApiLogout);
}
function init() {
return $async(initAsync);
}

View file

@ -126,6 +126,13 @@ angular.module('portainer.app').factory('LocalStorage', [
getJobImage: function () {
return localStorageService.get('job_image');
},
storeActiveTab: function (key, index) {
return localStorageService.set('active_tab_' + key, index);
},
getActiveTab: function (key) {
const activeTab = localStorageService.get('active_tab_' + key);
return activeTab === null ? 0 : activeTab;
},
storeLogoutReason: (reason) => localStorageService.set('logout_reason', reason),
getLogoutReason: () => localStorageService.get('logout_reason'),
cleanLogoutReason: () => localStorageService.remove('logout_reason'),

View file

@ -128,6 +128,21 @@ angular.module('portainer.app').factory('ModalService', [
});
};
service.confirmUpdate = function (message, callback) {
message = $sanitize(message);
service.confirm({
title: 'Are you sure ?',
message: message,
buttons: {
confirm: {
label: 'Update',
className: 'btn-warning',
},
},
callback: callback,
});
};
service.confirmContainerDeletion = function (title, callback) {
title = $sanitize(title);
prompt({

View file

@ -16,7 +16,9 @@ angular.module('portainer.app').factory('Notifications', [
service.error = function (title, e, fallbackText) {
var msg = fallbackText;
if (e.err && e.err.data && e.err.data.details) {
if (e.err && e.err.data && e.err.data.message) {
msg = e.err.data.message;
} else if (e.err && e.err.data && e.err.data.details) {
msg = e.err.data.details;
} else if (e.data && e.data.details) {
msg = e.data.details;
@ -26,8 +28,6 @@ angular.module('portainer.app').factory('Notifications', [
msg = e.data.content;
} else if (e.message) {
msg = e.message;
} else if (e.err && e.err.data && e.err.data.message) {
msg = e.err.data.message;
} else if (e.err && e.err.data && e.err.data.length > 0 && e.err.data[0].message) {
msg = e.err.data[0].message;
} else if (e.err && e.err.data && e.err.data.err) {

View file

@ -173,6 +173,12 @@ angular.module('portainer.app').factory('StateManager', [
LocalStorage.storeEndpointState(state.endpoint);
deferred.resolve();
return deferred.promise;
} else if (endpoint.Type === 5 || endpoint.Type === 6 || endpoint.Type === 7) {
state.endpoint.name = endpoint.Name;
state.endpoint.mode = { provider: 'KUBERNETES' };
LocalStorage.storeEndpointState(state.endpoint);
deferred.resolve();
return deferred.promise;
}
$q.all({

View file

@ -1,3 +1,4 @@
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
angular
@ -19,6 +20,7 @@ angular
$scope.state = {
EnvironmentType: 'agent',
actionInProgress: false,
deploymentTab: 0,
allowCreateTag: Authentication.isAdmin(),
availableEdgeAgentCheckinOptions: [
{ key: 'Use default interval', value: 0 },
@ -54,9 +56,12 @@ angular
};
$scope.copyAgentCommand = function () {
clipboard.copyText('curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent');
$('#copyNotification').show();
$('#copyNotification').fadeOut(2000);
if ($scope.state.deploymentTab === 0) {
clipboard.copyText('curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent');
} else {
clipboard.copyText('curl -L https://downloads.portainer.io/portainer-agent-k8s.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml');
}
$('#copyNotification').show().fadeOut(2500);
};
$scope.setDefaultPortainerInstanceURL = function () {
@ -67,6 +72,20 @@ angular
$scope.formValues.URL = '';
};
$scope.onCreateTag = function onCreateTag(tagName) {
return $async(onCreateTagAsync, tagName);
};
async function onCreateTagAsync(tagName) {
try {
const tag = await TagService.createTag(tagName);
$scope.availableTags = $scope.availableTags.concat(tag);
$scope.formValues.TagIds = $scope.formValues.TagIds.concat(tag.Id);
} catch (err) {
Notifications.error('Failue', err, 'Unable to create tag');
}
}
$scope.addDockerEndpoint = function () {
var name = $scope.formValues.Name;
var URL = $filter('stripprotocol')($scope.formValues.URL);
@ -83,7 +102,7 @@ angular
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
addEndpoint(name, 1, URL, publicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
addEndpoint(name, PortainerEndpointTypes.DockerEnvironment, URL, publicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
};
$scope.addAgentEndpoint = function () {
@ -93,7 +112,9 @@ angular
var groupId = $scope.formValues.GroupId;
var tagIds = $scope.formValues.TagIds;
addEndpoint(name, 2, URL, publicURL, groupId, tagIds, true, true, true, null, null, null);
addEndpoint(name, PortainerEndpointTypes.AgentOnDockerEnvironment, URL, publicURL, groupId, tagIds, true, true, true, null, null, null);
// TODO: k8s merge - temporarily updated to AgentOnKubernetesEnvironment, breaking Docker agent support
// addEndpoint(name, PortainerEndpointTypes.AgentOnKubernetesEnvironment, URL, publicURL, groupId, tags, true, true, true, null, null, null);
};
$scope.addEdgeAgentEndpoint = function () {
@ -102,7 +123,9 @@ angular
var tagIds = $scope.formValues.TagIds;
var URL = $scope.formValues.URL;
addEndpoint(name, 4, URL, '', groupId, tagIds, false, false, false, null, null, null, $scope.formValues.CheckinInterval);
addEndpoint(name, PortainerEndpointTypes.EdgeAgentOnDockerEnvironment, URL, '', groupId, tagIds, false, false, false, null, null, null, $scope.formValues.CheckinInterval);
// TODO: k8s merge - temporarily updated to EdgeAgentOnKubernetesEnvironment, breaking Docker Edge agent support
// addEndpoint(name, PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment, URL, "", groupId, tags, false, false, false, null, null, null);
};
$scope.addAzureEndpoint = function () {
@ -116,20 +139,6 @@ angular
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds);
};
$scope.onCreateTag = function onCreateTag(tagName) {
return $async(onCreateTagAsync, tagName);
};
async function onCreateTagAsync(tagName) {
try {
const tag = await TagService.createTag(tagName);
$scope.availableTags = $scope.availableTags.concat(tag);
$scope.formValues.TagIds = $scope.formValues.TagIds.concat(tag.Id);
} catch (err) {
Notifications.error('Failue', err, 'Unable to create tag');
}
}
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
$scope.state.actionInProgress = true;
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
@ -164,8 +173,10 @@ angular
)
.then(function success(data) {
Notifications.success('Endpoint created', name);
if (type === 4) {
if (type === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment || type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
$state.go('portainer.endpoints.endpoint', { id: data.Id });
} else if (type === PortainerEndpointTypes.AgentOnKubernetesEnvironment) {
$state.go('portainer.endpoints.endpoint.kubernetesConfig', { id: data.Id });
} else {
$state.go('portainer.endpoints', {}, { reload: true });
}

View file

@ -75,13 +75,25 @@
<span class="col-sm-12 text-muted small">
Ensure that you have deployed the Portainer agent in your cluster first. You can use execute the following command on any manager node to deploy it.
<div style="margin-top: 10px;">
<code>
curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent
</code>
<span class="btn btn-primary btn-sm space-left" ng-click="copyAgentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span>
<span>
<i id="copyNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i>
</span>
<uib-tabset active="state.deploymentTab">
<uib-tab index="0" heading="Kubernetes">
<code style="display: block; white-space: pre-wrap;">
curl -L https://downloads.portainer.io/portainer-agent-k8s.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml
</code>
</uib-tab>
<uib-tab index="0" heading="Docker">
<code>
curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent
</code>
</uib-tab>
</uib-tabset>
<div style="margin-top: 10px;">
<span class="btn btn-primary btn-sm space-left" ng-click="copyAgentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
<span>
<i id="copyNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i>
</span>
</div>
</div>
</span>
</div>
@ -92,8 +104,11 @@
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
Allows you to create an endpoint that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All the
required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
<p>
Allows you to create an endpoint that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All the
required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
</p>
<p> You can read more about the Edge agent in the userguide available <a href="https://downloads.portainer.io/edge_agent_guide.pdf">here.</a> </p>
</span>
</div>
</div>
@ -127,7 +142,15 @@
<div class="form-group">
<label for="container_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="container_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01" required auto-focus />
<input
type="text"
class="form-control"
name="container_name"
ng-model="formValues.Name"
placeholder="e.g. docker-prod01 / kubernetes-cluster01"
required
auto-focus
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.container_name.$invalid">
@ -146,7 +169,8 @@
<portainer-tooltip
position="bottom"
message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."
></portainer-tooltip>
>
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
@ -178,8 +202,8 @@
</div>
</div>
<!-- !endpoint-url-input -->
<!-- portainer-instance-input -->
<div ng-if="state.EnvironmentType === 'edge_agent'">
<!-- portainer-instance-input -->
<div class="form-group">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Portainer server URL
@ -203,7 +227,8 @@
<portainer-tooltip
position="bottom"
message="Interval used by this Edge agent to check in with the Portainer instance. Affects Edge endpoint management and Edge compute features."
></portainer-tooltip>
>
</portainer-tooltip>
</label>
<div class="col-sm-10">
<select
@ -223,7 +248,8 @@
<portainer-tooltip
position="bottom"
message="URL or IP address where exposed containers will be reachable. This field is optional and will default to the endpoint URL."
></portainer-tooltip>
>
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="endpoint_public_url" ng-model="formValues.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" />
@ -319,13 +345,8 @@
<!-- !group -->
<!-- tags -->
<div class="form-group">
<tag-selector
ng-if="formValues && availableTags"
tags="availableTags"
model="formValues.TagIds"
allow-create="state.allowCreateTag"
on-create="(onCreateTag)"
></tag-selector>
<tag-selector ng-if="formValues && availableTags" tags="availableTags" model="formValues.TagIds" allow-create="state.allowCreateTag" on-create="(onCreateTag)">
</tag-selector>
</div>
<!-- !tags -->
<div class="col-sm-12 form-section-title">

View file

@ -10,11 +10,11 @@
</rd-header>
<div class="row">
<information-panel ng-if="endpoint.Type === 4 && endpoint.EdgeID" title-text="Edge information">
<information-panel ng-if="state.edgeEndpoint && endpoint.EdgeID" title-text="Edge information">
<span class="small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This Edge endpoint is associated to an Edge environment.
This Edge endpoint is associated to an Edge environment {{ state.kubernetesEndpoint ? '(Kubernetes)' : '(Docker)' }}.
</p>
<p>
Edge key: <code>{{ endpoint.EdgeKey }}</code>
@ -24,11 +24,11 @@
</p>
</span>
</information-panel>
<information-panel ng-if="endpoint.Type === 4 && !endpoint.EdgeID" title-text="Deploy an agent">
<information-panel ng-if="state.edgeEndpoint && !endpoint.EdgeID" title-text="Deploy an agent">
<span class="small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Deploy the Edge agent on your remote Docker environment using the following command(s)
Deploy the Edge agent on your remote Docker/Kubernetes environment using the following command(s)
</p>
<p>
The agent will communicate with Portainer via <u>{{ edgeKeyDetails.instanceURL }}</u> and <u>tcp://{{ edgeKeyDetails.tunnelServerAddr }}</u>
@ -41,6 +41,11 @@
<uib-tab index="1" heading="Swarm">
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{ dockerCommands.swarm }}</code>
</uib-tab>
<uib-tab index="0" heading="Kubernetes">
<code style="display: block; white-space: pre-wrap;">
curl https://downloads.portainer.io/portainer-edge-agent-setup.sh | sudo bash -s -- {{ randomEdgeID }} {{ endpoint.EdgeKey }}
</code>
</uib-tab>
</uib-tabset>
<div style="margin-top: 10px;">
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentDeploymentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
@ -66,6 +71,13 @@
</div>
</span>
</information-panel>
<information-panel ng-if="state.kubernetesEndpoint && (!state.edgeEndpoint || (state.edgeEndpoint && endpoint.EdgeID))" title-text="Kubernetes features configuration">
<span class="small text-muted">
<i class="fa fa-tools blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
You should configure the features available in this Kubernetes environment in the
<a ui-sref="portainer.endpoints.endpoint.kubernetesConfig({id: endpoint.Id})">Kubernetes configuration</a> view.
</span>
</information-panel>
</div>
<div class="row">
@ -80,22 +92,23 @@
<div class="form-group">
<label for="container_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="container_name" ng-model="endpoint.Name" placeholder="e.g. docker-prod01" />
<input type="text" class="form-control" id="container_name" ng-model="endpoint.Name" placeholder="e.g. kubernetes-cluster01 / docker-prod01" />
</div>
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group" ng-if="endpoint.Type !== 4">
<div class="form-group" ng-if="!state.edgeEndpoint">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Endpoint URL
<portainer-tooltip
position="bottom"
message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."
></portainer-tooltip>
>
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
ng-disabled="endpointType === 'local' || endpoint.Type === 3"
ng-disabled="endpointType === 'local' || state.azureEndpoint"
type="text"
class="form-control"
id="endpoint_url"
@ -106,19 +119,20 @@
</div>
<!-- !endpoint-url-input -->
<!-- endpoint-public-url-input -->
<div class="form-group" ng-if="endpoint.Type !== 3">
<div class="form-group" ng-if="!state.azureEndpoint">
<label for="endpoint_public_url" class="col-sm-3 col-lg-2 control-label text-left">
Public IP
<portainer-tooltip
position="bottom"
message="URL or IP address where exposed containers will be reachable. This field is optional and will default to the endpoint URL."
></portainer-tooltip>
>
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="endpoint_public_url" ng-model="endpoint.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" />
</div>
</div>
<div class="form-group" ng-if="endpoint.Type === 4">
<div class="form-group" ng-if="state.edgeEndpoint">
<label for="edge_checkin" class="col-sm-2 control-label text-left">
Poll frequency
<portainer-tooltip
@ -135,13 +149,13 @@
></select>
</div>
</div>
<!-- !endpoint-public-url-input -->
<azure-endpoint-config
ng-if="endpoint.Type === 3"
ng-if="state.azureEndpoint"
application-id="endpoint.AzureCredentials.ApplicationID"
tenant-id="endpoint.AzureCredentials.TenantID"
authentication-key="endpoint.AzureCredentials.AuthenticationKey"
></azure-endpoint-config>
<!-- !endpoint-public-url-input -->
<div class="col-sm-12 form-section-title">
Metadata
</div>
@ -161,7 +175,7 @@
</div>
<!-- !tags -->
<!-- endpoint-security -->
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3 && endpoint.Type !== 4">
<div ng-if="endpointType === 'remote' && !state.azureEndpoint && !state.edgeEndpoint && endpoint.Type !== 6">
<div class="col-sm-12 form-section-title">
Security
</div>

View file

@ -1,5 +1,6 @@
import _ from 'lodash-es';
import uuidv4 from 'uuid/v4';
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
angular
@ -24,6 +25,10 @@ angular
uploadInProgress: false,
actionInProgress: false,
deploymentTab: 0,
azureEndpoint: false,
kubernetesEndpoint: false,
agentEndpoint: false,
edgeEndpoint: false,
allowCreate: Authentication.isAdmin(),
availableEdgeAgentCheckinOptions: [
{ key: 'Use default interval', value: 0 },
@ -58,7 +63,7 @@ angular
$scope.endpoint.EdgeKey +
' -e CAP_HOST_MANAGEMENT=1 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent'
);
} else {
} else if ($scope.state.deploymentTab === 1) {
clipboard.copyText(
'docker network create --driver overlay portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e EDGE_ID=' +
$scope.randomEdgeID +
@ -66,6 +71,8 @@ angular
$scope.endpoint.EdgeKey +
" -e CAP_HOST_MANAGEMENT=1 --mode global --constraint 'node.platform.os == linux' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes --mount type=bind,src=//,dst=/host --mount type=volume,src=portainer_agent_data,dst=/data portainer/agent"
);
} else {
clipboard.copyText('curl https://downloads.portainer.io/portainer-edge-agent-setup.sh | bash -s -- ' + $scope.randomEdgeID + ' ' + $scope.endpoint.EdgeKey);
}
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
};
@ -114,7 +121,12 @@ angular
AzureAuthenticationKey: endpoint.AzureCredentials.AuthenticationKey,
};
if ($scope.endpointType !== 'local' && endpoint.Type !== 3) {
if (
$scope.endpointType !== 'local' &&
endpoint.Type !== PortainerEndpointTypes.AzureEnvironment &&
endpoint.Type !== PortainerEndpointTypes.KubernetesLocalEnvironment &&
endpoint.Type !== PortainerEndpointTypes.AgentOnKubernetesEnvironment
) {
payload.URL = 'tcp://' + endpoint.URL;
}
@ -151,6 +163,30 @@ angular
return keyInformation;
}
function configureState() {
if (
$scope.endpoint.Type === PortainerEndpointTypes.KubernetesLocalEnvironment ||
$scope.endpoint.Type === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
$scope.endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
) {
$scope.state.kubernetesEndpoint = true;
}
if ($scope.endpoint.Type === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment || $scope.endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
$scope.state.edgeEndpoint = true;
}
if ($scope.endpoint.Type === PortainerEndpointTypes.AzureEnvironment) {
$scope.state.azureEndpoint = true;
}
if (
$scope.endpoint.Type === PortainerEndpointTypes.AgentOnDockerEnvironment ||
$scope.endpoint.Type === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment ||
$scope.endpoint.Type === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
$scope.endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
) {
$scope.state.agentEndpoint = true;
}
}
function initView() {
$q.all({
endpoint: EndpointService.endpoint($transition$.params().id),
@ -166,7 +202,7 @@ angular
$scope.endpointType = 'remote';
}
endpoint.URL = $filter('stripprotocol')(endpoint.URL);
if (endpoint.Type === 4) {
if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment || endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
$scope.edgeKeyDetails = decodeEdgeKey(endpoint.EdgeKey);
$scope.randomEdgeID = uuidv4();
$scope.dockerCommands = {
@ -180,6 +216,7 @@ angular
$scope.endpoint = endpoint;
$scope.groups = data.groups;
$scope.availableTags = data.tags;
configureState();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');

View file

@ -10,6 +10,8 @@
<motd-panel ng-if="motd && motd.Message !== '' && applicationState.UI.dismissedInfoHash !== motd.Hash" motd="motd" dismiss-action="dismissImportantInformation(motd.Hash)">
</motd-panel>
<kubernetes-feedback-panel></kubernetes-feedback-panel>
<information-panel ng-if="!isAdmin && endpoints.length === 0" title-text="Information">
<span class="small text-muted">
<p>

View file

@ -15,7 +15,8 @@ angular
LegacyExtensionManager,
ModalService,
MotdService,
SystemService
SystemService,
KubernetesHealthService
) {
$scope.state = {
connectingToEdgeEndpoint: false,
@ -30,6 +31,10 @@ angular
return switchToAzureEndpoint(endpoint);
} else if (endpoint.Type === 4) {
return switchToEdgeEndpoint(endpoint);
} else if (endpoint.Type === 5 || endpoint.Type === 6) {
return switchToKubernetesEndpoint(endpoint);
} else if (endpoint.Type === 7) {
return switchToKubernetesEdgeEndpoint(endpoint);
}
checkEndpointStatus(endpoint)
@ -102,6 +107,17 @@ angular
});
}
function switchToKubernetesEndpoint(endpoint) {
EndpointProvider.setEndpointID(endpoint.Id);
StateManager.updateEndpointState(endpoint, [])
.then(function success() {
$state.go('kubernetes.dashboard');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Kubernetes endpoint');
});
}
function switchToEdgeEndpoint(endpoint) {
if (!endpoint.EdgeID) {
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
@ -121,6 +137,26 @@ angular
});
}
function switchToKubernetesEdgeEndpoint(endpoint) {
if (!endpoint.EdgeID) {
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
return;
}
EndpointProvider.setEndpointID(endpoint.Id);
$scope.state.connectingToEdgeEndpoint = true;
KubernetesHealthService.ping()
.then(function success() {
endpoint.Status = 1;
})
.catch(function error() {
endpoint.Status = 2;
})
.finally(function final() {
switchToKubernetesEndpoint(endpoint);
});
}
function switchToDockerEndpoint(endpoint) {
if (endpoint.Status === 2 && endpoint.Snapshots[0] && endpoint.Snapshots[0].Swarm === true) {
$scope.state.connectingToEdgeEndpoint = false;

View file

@ -0,0 +1,34 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Connect directly to a Portainer agent running inside a Docker or Kubernetes environment.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="ctrl.formValues.Name" placeholder="e.g. prod-cluster-01" />
</div>
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group">
<label for="endpoint_url" class="col-sm-4 col-lg-3 control-label text-left">
Agent URL
<portainer-tooltip position="bottom" message="URL or IP address of a Portainer agent."> </portainer-tooltip>
</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_url" ng-model="ctrl.formValues.URL" placeholder="e.g. 10.0.0.10:9001" />
</div>
</div>
<!-- !endpoint-url-input -->

View file

@ -0,0 +1,63 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-muted"> <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental. </p>
<p class="text-primary">
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
</p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Have a look at
<a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank">the Azure documentation</a>
to retrieve the credentials required below.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="ctrl.formValues.Name" placeholder="e.g. azure-01" />
</div>
</div>
<!-- !name-input -->
<div class="col-sm-12 form-section-title">
Azure credentials
</div>
<!-- applicationId-input -->
<div class="form-group">
<label for="azure_credential_appid" class="col-sm-4 col-lg-3 control-label text-left">Application ID</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="azure_credential_appid" ng-model="ctrl.formValues.AzureApplicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</div>
</div>
<!-- !applicationId-input -->
<!-- tenantId-input -->
<div class="form-group">
<label for="azure_credential_tenantid" class="col-sm-4 col-lg-3 control-label text-left">Tenant ID</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="azure_credential_tenantid" ng-model="ctrl.formValues.AzureTenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</div>
</div>
<!-- !tenantId-input -->
<!-- authenticationkey-input -->
<div class="form-group">
<label for="azure_credential_authkey" class="col-sm-4 col-lg-3 control-label text-left">Authentication key</label>
<div class="col-sm-8 col-lg-9">
<input
type="text"
class="form-control"
id="azure_credential_authkey"
ng-model="ctrl.formValues.AzureAuthenticationKey"
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
/>
</div>
</div>
<!-- !authenticationkey-input -->

View file

@ -0,0 +1,21 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Manage the Docker environment where Portainer is running.
</p>
<p class="text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Ensure that you have started the Portainer container with the following Docker flag:
</p>
<p class="text-muted"> <code>-v "/var/run/docker.sock:/var/run/docker.sock"</code> (Linux). </p>
<p class="text-muted">
or
</p>
<p class="text-muted"> <code>-v \\.\pipe\docker_engine:\\.\pipe\docker_engine</code> (Windows). </p>
</span>
</div>
</div>

View file

@ -0,0 +1,12 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Manage the Kubernetes environment where Portainer is running.
</p>
</span>
</div>
</div>

View file

@ -0,0 +1,120 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Connect Portainer to a remote Docker environment using the Docker API over TCP.
</p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
The Docker API must be exposed over TCP. You can find more information about how to expose the Docker API over TCP
<a href="https://docs.docker.com/engine/security/https/" target="_blank">in the Docker documentation</a>.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="ctrl.formValues.Name" placeholder="e.g. docker-prod01" />
</div>
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group">
<label for="endpoint_url" class="col-sm-4 col-lg-3 control-label text-left">
Endpoint URL
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host with API exposed over TCP."></portainer-tooltip>
</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_url" ng-model="ctrl.formValues.URL" placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375" />
</div>
</div>
<!-- !endpoint-url-input -->
<!-- tls-checkbox -->
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
TLS
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the Docker endpoint."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="ctrl.formValues.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
<!-- tls-options -->
<div ng-if="ctrl.formValues.TLS">
<!-- skip-server-verification -->
<div class="form-group">
<div class="col-sm-10">
<label for="tls_verify" class="control-label text-left">
Skip server verification
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate server based on given CA."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="ctrl.formValues.TLSSkipVerify" /><i></i> </label>
</div>
</div>
<!-- !skip-server-verification -->
<!-- skip-client-verification -->
<div class="form-group">
<div class="col-sm-10">
<label for="tls_client_cert" class="control-label text-left">
Skip client verification
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate with a client certificate."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="ctrl.formValues.TLSSKipClientVerify" /><i></i> </label>
</div>
</div>
<!-- !skip-client-verification -->
<div class="col-sm-12 form-section-title" ng-if="!ctrl.formValues.TLSSkipVerify || !ctrl.formValues.TLSSKipClientVerify">
Required TLS files
</div>
<!-- ca-input -->
<div class="form-group" ng-if="!ctrl.formValues.TLSSkipVerify">
<label class="col-sm-4 col-lg-3 control-label text-left">TLS CA certificate</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="ctrl.formValues.TLSCACert">Select file</button>
<span style="margin-left: 5px;">
{{ ctrl.formValues.TLSCACert.name }}
<i class="fa fa-times red-icon" ng-if="!ctrl.formValues.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="ctrl.state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !ca-input -->
<div ng-if="!ctrl.formValues.TLSSKipClientVerify">
<!-- cert-input -->
<div class="form-group">
<label for="tls_cert" class="col-sm-4 col-lg-3 control-label text-left">TLS certificate</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="ctrl.formValues.TLSCert">Select file</button>
<span style="margin-left: 5px;">
{{ ctrl.formValues.TLSCert.name }}
<i class="fa fa-times red-icon" ng-if="!ctrl.formValues.TLSCert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="ctrl.state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !cert-input -->
<!-- key-input -->
<div class="form-group">
<label class="col-sm-4 col-lg-3 control-label text-left">TLS key</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="ctrl.formValues.TLSKey">Select file</button>
<span style="margin-left: 5px;">
{{ ctrl.formValues.TLSKey.name }}
<i class="fa fa-times red-icon" ng-if="!ctrl.formValues.TLSKey" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="ctrl.state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !key-input -->
</div>
</div>
<!-- !tls-options -->

View file

@ -4,8 +4,8 @@
<div class="col-sm-12">
<!-- simple box logo -->
<div class="row">
<img ng-if="logo" ng-src="{{ logo }}" class="simple-box-logo" />
<img ng-if="!logo" src="~@/assets/images/logo_alt.png" class="simple-box-logo" alt="Portainer" />
<img ng-if="ctrl.logo" ng-src="{{ ctrl.logo }}" class="simple-box-logo" />
<img ng-if="!ctrl.logo" src="~@/assets/images/logo_alt.png" class="simple-box-logo" alt="Portainer" />
</div>
<!-- !simple box logo -->
<!-- init-endpoint panel -->
@ -17,7 +17,7 @@
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
Connect Portainer to the Docker environment you want to manage.
Connect Portainer to the container environment you want to manage.
</span>
</div>
</div>
@ -25,377 +25,52 @@
<!-- endpoint-type -->
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="local_endpoint" ng-model="formValues.EndpointType" value="local" />
<label for="local_endpoint">
<div ng-repeat="type in ctrl.endpointSections">
<input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" />
<label for="{{ type.Id }}">
<div class="boxselector_header">
<i class="fab fa-docker" aria-hidden="true" style="margin-right: 2px;"></i>
Local
<i ng-class="type.Classes" aria-hidden="true" style="margin-right: 2px;"></i>
{{ type.Title }}
</div>
<p>Manage the local Docker environment</p>
</label>
</div>
<div>
<input type="radio" id="remote_endpoint" ng-model="formValues.EndpointType" value="remote" />
<label for="remote_endpoint">
<div class="boxselector_header">
<i class="fab fa-docker" aria-hidden="true" style="margin-right: 2px;"></i>
Remote
</div>
<p>Manage a remote Docker environment</p>
</label>
</div>
<div>
<input type="radio" id="agent_endpoint" ng-model="formValues.EndpointType" value="agent" />
<label for="agent_endpoint">
<div class="boxselector_header">
<i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i>
Agent
</div>
<p>Connect to a Portainer agent</p>
</label>
</div>
<div>
<input type="radio" id="azure_endpoint" ng-model="formValues.EndpointType" value="azure" />
<label for="azure_endpoint">
<div class="boxselector_header">
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
Azure
</div>
<p>Connect to Microsoft Azure ACI</p>
<p>{{ type.Description }}</p>
</label>
</div>
</div>
</div>
<!-- !endpoint-type -->
<!-- local-endpoint -->
<div ng-if="formValues.EndpointType === 'local'">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Manage the Docker environment where Portainer is running.
</p>
<p class="text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Ensure that you have started the Portainer container with the following Docker flag:
</p>
<p class="text-muted"> <code>-v "/var/run/docker.sock:/var/run/docker.sock"</code> (Linux). </p>
<p class="text-muted">
or
</p>
<p class="text-muted"> <code>-v \\.\pipe\docker_engine:\\.\pipe\docker_engine</code> (Windows). </p>
</span>
</div>
</div>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress"
ng-click="createLocalEndpoint()"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
<span ng-show="state.actionInProgress">Connecting...</span>
</button>
</div>
</div>
<!-- !actions -->
<!-- endpoint-type-details -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.DOCKER_LOCAL">
<ng-include src="'app/portainer/views/init/endpoint/includes/localDocker.html'"></ng-include>
</div>
<!-- !local-endpoint -->
<!-- agent-endpoint -->
<div ng-if="formValues.EndpointType === 'agent'">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Connect directly to a Portainer agent running inside a Swarm cluster.
</p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
If you have started Portainer in the same overlay network as the agent, you can use <code>tasks.AGENT_SERVICE_NAME:AGENT_SERVICE_PORT</code> as the endpoint
URL format.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01" />
</div>
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group">
<label for="endpoint_url" class="col-sm-4 col-lg-3 control-label text-left">
Agent URL
<portainer-tooltip position="bottom" message="URL or IP address of a Portainer agent."></portainer-tooltip>
</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001" />
</div>
</div>
<!-- !endpoint-url-input -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !formValues.Name || !formValues.URL"
ng-click="createAgentEndpoint()"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
<span ng-show="state.actionInProgress">Connecting...</span>
</button>
</div>
</div>
<!-- !actions -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.KUBERNETES_LOCAL">
<ng-include src="'app/portainer/views/init/endpoint/includes/localKubernetes.html'"></ng-include>
</div>
<!-- !agent-endpoint -->
<!-- azure-endpoint -->
<div ng-if="formValues.EndpointType === 'azure'">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-muted"> <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental. </p>
<p class="text-primary">
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
</p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Have a look at
<a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank"
>the Azure documentation</a
>
to retrieve the credentials required below.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. azure-01" />
</div>
</div>
<!-- !name-input -->
<div class="col-sm-12 form-section-title">
Azure credentials
</div>
<!-- applicationId-input -->
<div class="form-group">
<label for="azure_credential_appid" class="col-sm-4 col-lg-3 control-label text-left">Application ID</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="azure_credential_appid" ng-model="formValues.AzureApplicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</div>
</div>
<!-- !applicationId-input -->
<!-- tenantId-input -->
<div class="form-group">
<label for="azure_credential_tenantid" class="col-sm-4 col-lg-3 control-label text-left">Tenant ID</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="azure_credential_tenantid" ng-model="formValues.AzureTenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</div>
</div>
<!-- !tenantId-input -->
<!-- authenticationkey-input -->
<div class="form-group">
<label for="azure_credential_authkey" class="col-sm-4 col-lg-3 control-label text-left">Authentication key</label>
<div class="col-sm-8 col-lg-9">
<input
type="text"
class="form-control"
id="azure_credential_authkey"
ng-model="formValues.AzureAuthenticationKey"
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
/>
</div>
</div>
<!-- !authenticationkey-input -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !formValues.Name || !formValues.AzureApplicationId || !formValues.AzureTenantId || !formValues.AzureAuthenticationKey"
ng-click="createAzureEndpoint()"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
<span ng-show="state.actionInProgress">Connecting...</span>
</button>
</div>
</div>
<!-- !actions -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.REMOTE">
<ng-include src="'app/portainer/views/init/endpoint/includes/remote.html'"></ng-include>
</div>
<!-- !azure-endpoint -->
<!-- remote-endpoint -->
<div ng-if="formValues.EndpointType === 'remote'">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-primary">
Connect Portainer to a remote Docker environment using the Docker API over TCP.
</p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
The Docker API must be exposed over TCP. You can find more information about how to expose the Docker API over TCP
<a href="https://docs.docker.com/engine/security/https/" target="_blank">in the Docker documentation</a>.
</p>
</span>
</div>
</div>
<div class="col-sm-12 form-section-title">
Environment
</div>
<!-- name-input -->
<div class="form-group">
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01" />
</div>
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group">
<label for="endpoint_url" class="col-sm-4 col-lg-3 control-label text-left">
Endpoint URL
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host with API exposed over TCP."></portainer-tooltip>
</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375" />
</div>
</div>
<!-- !endpoint-url-input -->
<!-- tls-checkbox -->
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
TLS
<portainer-tooltip
position="bottom"
message="Enable this option if you need to specify TLS certificates to connect to the Docker endpoint."
></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
<!-- tls-options -->
<div ng-if="formValues.TLS">
<!-- skip-server-verification -->
<div class="form-group">
<div class="col-sm-10">
<label for="tls_verify" class="control-label text-left">
Skip server verification
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate server based on given CA."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.TLSSkipVerify" /><i></i> </label>
</div>
</div>
<!-- !skip-server-verification -->
<!-- skip-client-verification -->
<div class="form-group">
<div class="col-sm-10">
<label for="tls_client_cert" class="control-label text-left">
Skip client verification
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate with a client certificate."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.TLSSKipClientVerify" /><i></i> </label>
</div>
</div>
<!-- !skip-client-verification -->
<div class="col-sm-12 form-section-title" ng-if="!formValues.TLSSkipVerify || !formValues.TLSSKipClientVerify">
Required TLS files
</div>
<!-- ca-input -->
<div class="form-group" ng-if="!formValues.TLSSkipVerify">
<label class="col-sm-4 col-lg-3 control-label text-left">TLS CA certificate</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
<span style="margin-left: 5px;">
{{ formValues.TLSCACert.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !ca-input -->
<div ng-if="!formValues.TLSSKipClientVerify">
<!-- cert-input -->
<div class="form-group">
<label for="tls_cert" class="col-sm-4 col-lg-3 control-label text-left">TLS certificate</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
<span style="margin-left: 5px;">
{{ formValues.TLSCert.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !cert-input -->
<!-- key-input -->
<div class="form-group">
<label class="col-sm-4 col-lg-3 control-label text-left">TLS key</label>
<div class="col-sm-8 col-lg-9">
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
<span style="margin-left: 5px;">
{{ formValues.TLSKey.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !key-input -->
</div>
</div>
<!-- !tls-options -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !formValues.Name || !formValues.URL || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (!formValues.TLSSKipClientVerify && (!formValues.TLSCert || !formValues.TLSKey))))"
ng-click="createRemoteEndpoint()"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
<span ng-show="state.actionInProgress">Connecting...</span>
</button>
</div>
</div>
<!-- !actions -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.AZURE">
<ng-include src="'app/portainer/views/init/endpoint/includes/azure.html'"></ng-include>
</div>
<!-- !remote-endpoint -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.AGENT">
<ng-include src="'app/portainer/views/init/endpoint/includes/agent.html'"></ng-include>
</div>
<!-- !endpoint-type-details -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="ctrl.isConnectButtonDisabled()"
ng-click="ctrl.createEndpoint()"
button-spinner="ctrl.state.actionInProgress"
>
<span ng-hide="ctrl.state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
<span ng-show="ctrl.state.actionInProgress">Connecting...</span>
</button>
</div>
</div>
<!-- !actions -->
</form>
<!-- !init-endpoint form -->
</div>

View file

@ -1,109 +1,223 @@
import _ from 'lodash-es';
import angular from 'angular';
import { PortainerEndpointInitFormValues, PortainerEndpointInitFormValueEndpointSections } from 'Portainer/models/endpoint/formValues';
import { PortainerEndpointTypes, PortainerEndpointConnectionTypes } from 'Portainer/models/endpoint/models';
angular.module('portainer.app').controller('InitEndpointController', [
'$scope',
'$state',
'EndpointService',
'StateManager',
'Notifications',
function ($scope, $state, EndpointService, StateManager, Notifications) {
if (!_.isEmpty($scope.applicationState.endpoint)) {
$state.go('portainer.home');
require('./includes/localDocker.html');
require('./includes/localKubernetes.html');
require('./includes/remote.html');
require('./includes/azure.html');
require('./includes/agent.html');
class InitEndpointController {
/* @ngInject */
constructor($async, $scope, $state, EndpointService, EndpointProvider, StateManager, Notifications) {
this.$async = $async;
this.$scope = $scope;
this.$state = $state;
this.EndpointService = EndpointService;
this.EndpointProvider = EndpointProvider;
this.StateManager = StateManager;
this.Notifications = Notifications;
this.createLocalEndpointAsync = this.createLocalEndpointAsync.bind(this);
this.createLocalKubernetesEndpointAsync = this.createLocalKubernetesEndpointAsync.bind(this);
this.createAgentEndpointAsync = this.createAgentEndpointAsync.bind(this);
this.createAzureEndpointAsync = this.createAzureEndpointAsync.bind(this);
this.createRemoteEndpointAsync = this.createRemoteEndpointAsync.bind(this);
}
$onInit() {
if (!_.isEmpty(this.$scope.applicationState.endpoint)) {
this.$state.go('portainer.home');
}
this.logo = this.StateManager.getState().application.logo;
$scope.logo = StateManager.getState().application.logo;
$scope.state = {
this.state = {
uploadInProgress: false,
actionInProgress: false,
};
$scope.formValues = {
EndpointType: 'remote',
Name: '',
URL: '',
TLS: false,
TLSSkipVerify: false,
TLSSKipClientVerify: false,
TLSCACert: null,
TLSCert: null,
TLSKey: null,
AzureApplicationId: '',
AzureTenantId: '',
AzureAuthenticationKey: '',
};
this.formValues = new PortainerEndpointInitFormValues();
this.endpointSections = PortainerEndpointInitFormValueEndpointSections;
this.PortainerEndpointConnectionTypes = PortainerEndpointConnectionTypes;
}
$scope.createLocalEndpoint = function () {
$scope.state.actionInProgress = true;
EndpointService.createLocalEndpoint()
.then(function success() {
$state.go('portainer.home');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
};
isRemoteConnectButtonDisabled() {
return (
this.state.actionInProgress ||
!this.formValues.Name ||
!this.formValues.URL ||
(this.formValues.TLS &&
((this.formValues.TLSVerify && !this.formValues.TLSCACert) || (!this.formValues.TLSSKipClientVerify && (!this.formValues.TLSCert || !this.formValues.TLSKey))))
);
}
$scope.createAzureEndpoint = function () {
var name = $scope.formValues.Name;
var applicationId = $scope.formValues.AzureApplicationId;
var tenantId = $scope.formValues.AzureTenantId;
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
isAzureConnectButtonDisabled() {
return this.state.actionInProgress || !this.formValues.Name || !this.formValues.AzureApplicationId || !this.formValues.AzureTenantId || !this.formValues.AzureAuthenticationKey;
}
createAzureEndpoint(name, applicationId, tenantId, authenticationKey);
};
$scope.createAgentEndpoint = function () {
var name = $scope.formValues.Name;
var URL = $scope.formValues.URL;
var PublicURL = URL.split(':')[0];
createRemoteEndpoint(name, 2, URL, PublicURL, true, true, true, null, null, null);
};
$scope.createRemoteEndpoint = function () {
var name = $scope.formValues.Name;
var URL = $scope.formValues.URL;
var PublicURL = URL.split(':')[0];
var TLS = $scope.formValues.TLS;
var TLSSkipVerify = TLS && $scope.formValues.TLSSkipVerify;
var TLSSKipClientVerify = TLS && $scope.formValues.TLSSKipClientVerify;
var TLSCAFile = TLSSkipVerify ? null : $scope.formValues.TLSCACert;
var TLSCertFile = TLSSKipClientVerify ? null : $scope.formValues.TLSCert;
var TLSKeyFile = TLSSKipClientVerify ? null : $scope.formValues.TLSKey;
createRemoteEndpoint(name, 1, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
};
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) {
$scope.state.actionInProgress = true;
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, 1, [])
.then(function success() {
$state.go('portainer.home');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Azure environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
isConnectButtonDisabled() {
switch (this.formValues.ConnectionType) {
case PortainerEndpointConnectionTypes.DOCKER_LOCAL:
return this.state.actionInProgress;
case PortainerEndpointConnectionTypes.KUBERNETES_LOCAL:
return this.state.actionInProgress;
case PortainerEndpointConnectionTypes.REMOTE:
return this.isRemoteConnectButtonDisabled();
case PortainerEndpointConnectionTypes.AZURE:
return this.isAzureConnectButtonDisabled();
case PortainerEndpointConnectionTypes.AGENT:
return this.state.actionInProgress || !this.formValues.Name || !this.formValues.URL;
default:
break;
}
}
function createRemoteEndpoint(name, type, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
$scope.state.actionInProgress = true;
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success() {
$state.go('portainer.home');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
createEndpoint() {
switch (this.formValues.ConnectionType) {
case PortainerEndpointConnectionTypes.DOCKER_LOCAL:
return this.createLocalEndpoint();
case PortainerEndpointConnectionTypes.KUBERNETES_LOCAL:
return this.createLocalKubernetesEndpoint();
case PortainerEndpointConnectionTypes.REMOTE:
return this.createRemoteEndpoint();
case PortainerEndpointConnectionTypes.AZURE:
return this.createAzureEndpoint();
case PortainerEndpointConnectionTypes.AGENT:
return this.createAgentEndpoint();
default:
this.Notifications.error('Failure', 'Unable to determine wich action to do');
}
},
]);
}
/**
* DOCKER_LOCAL (1)
*/
async createLocalEndpointAsync() {
try {
this.state.actionInProgress = true;
await this.EndpointService.createLocalEndpoint();
this.$state.go('portainer.home');
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
} finally {
this.state.actionInProgress = false;
}
}
createLocalEndpoint() {
return this.$async(this.createLocalEndpointAsync);
}
/**
* KUBERNETES_LOCAL (5)
*/
async createLocalKubernetesEndpointAsync() {
try {
this.state.actionInProgress = true;
const endpoint = await this.EndpointService.createLocalKubernetesEndpoint();
this.$state.go('portainer.endpoints.endpoint.kubernetesConfig', { id: endpoint.Id });
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to connect to the Kubernetes environment');
} finally {
this.state.actionInProgress = false;
}
}
createLocalKubernetesEndpoint() {
return this.$async(this.createLocalKubernetesEndpointAsync);
}
/**
* DOCKER / KUBERNETES AGENT (2 / 6)
*/
async createAgentEndpointAsync() {
try {
this.state.actionInProgress = true;
const name = this.formValues.Name;
const URL = this.formValues.URL;
const PublicURL = URL.split(':')[0];
// TODO: k8s merge - change type ID for agent on kube (6) or agent on swarm (2)
const endpoint = await this.EndpointService.createRemoteEndpoint(
name,
PortainerEndpointTypes.AgentOnKubernetesEnvironment,
URL,
PublicURL,
1,
[],
true,
true,
true,
null,
null,
null
);
// TODO: k8s merge - go on home whith agent on swarm (2)
this.$state.go('portainer.endpoints.endpoint.kubernetesConfig', { id: endpoint.Id });
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
} finally {
this.state.actionInProgress = false;
}
}
createAgentEndpoint() {
return this.$async(this.createAgentEndpointAsync);
}
/**
* DOCKER REMOTE (1)
*/
async createRemoteEndpointAsync() {
try {
this.state.actionInProgress = true;
const name = this.formValues.Name;
const type = PortainerEndpointTypes.DockerEnvironment;
const URL = this.formValues.URL;
const PublicURL = URL.split(':')[0];
const TLS = this.formValues.TLS;
const TLSSkipVerify = TLS && this.formValues.TLSSkipVerify;
const TLSSKipClientVerify = TLS && this.formValues.TLSSKipClientVerify;
const TLSCAFile = TLSSkipVerify ? null : this.formValues.TLSCACert;
const TLSCertFile = TLSSKipClientVerify ? null : this.formValues.TLSCert;
const TLSKeyFile = TLSSKipClientVerify ? null : this.formValues.TLSKey;
await this.EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
this.$state.go('portainer.home');
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
} finally {
this.state.actionInProgress = false;
}
}
createRemoteEndpoint() {
return this.$async(this.createAgentEndpointAsync);
}
/**
* AZURE (4)
*/
async createAzureEndpointAsync() {
try {
this.state.actionInProgress = true;
var name = this.formValues.Name;
var applicationId = this.formValues.AzureApplicationId;
var tenantId = this.formValues.AzureTenantId;
var authenticationKey = this.formValues.AzureAuthenticationKey;
await this.EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, 1, []);
this.$state.go('portainer.home');
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to connect to the Azure environment');
} finally {
this.state.actionInProgress = false;
}
}
createAzureEndpoint() {
return this.$async(this.createAgentEndpointAsync);
}
}
export default InitEndpointController;
angular.module('portainer.app').controller('InitEndpointController', InitEndpointController);

View file

@ -0,0 +1,16 @@
<div class="page-wrapper">
<!-- login box -->
<div class="container simple-box">
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
<!-- login box logo -->
<div class="row">
<img ng-if="!ctrl.logo" src="~@/assets/images/logo_alt.png" class="simple-box-logo" alt="Portainer" />
<img ng-if="ctrl.logo" ng-src="{{ ctrl.logo }}" class="simple-box-logo" />
</div>
<div class="row" style="text-align: center;">
Logout in progress...
<i class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,61 @@
import angular from 'angular';
class LogoutController {
/* @ngInject */
constructor($async, $state, $transition$, Authentication, StateManager, Notifications, LocalStorage) {
this.$async = $async;
this.$state = $state;
this.$transition$ = $transition$;
this.Authentication = Authentication;
this.StateManager = StateManager;
this.Notifications = Notifications;
this.LocalStorage = LocalStorage;
this.logo = this.StateManager.getState().application.logo;
this.logoutAsync = this.logoutAsync.bind(this);
this.onInit = this.onInit.bind(this);
}
/**
* UTILS FUNCTIONS SECTION
*/
async logoutAsync() {
const error = this.$transition$.params().error;
const performApiLogout = this.$transition$.params().performApiLogout;
try {
await this.Authentication.logout(performApiLogout);
} finally {
this.LocalStorage.storeLogoutReason(error);
this.$state.go('portainer.auth', { reload: true });
}
}
logout() {
return this.$async(this.logoutAsync);
}
/**
* END UTILS FUNCTIONS SECTION
*/
async onInit() {
try {
await this.logout();
} catch (err) {
this.Notifications.error('Failure', err, 'An error occured during logout');
}
}
$onInit() {
return this.$async(this.onInit);
}
/**
* END ON INIT SECTION
*/
}
export default LogoutController;
angular.module('portainer.app').controller('LogoutController', LogoutController);

View file

@ -13,9 +13,11 @@
<a ui-sref="portainer.home" ui-sref-active="active">Home <span class="menu-icon fa fa-home fa-fw"></span></a>
</li>
<li class="sidebar-title endpoint-name" ng-if="applicationState.endpoint.name"> <span class="fa fa-plug space-right"></span>{{ applicationState.endpoint.name }} </li>
<kubernetes-sidebar-content ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'KUBERNETES'" admin-access="isAdmin">
</kubernetes-sidebar-content>
<azure-sidebar-content ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'AZURE'"> </azure-sidebar-content>
<docker-sidebar-content
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE'"
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
endpoint-api-version="applicationState.endpoint.apiVersion"
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'"

View file

@ -58,7 +58,7 @@
>
</div>
<div class="col-sm-12 col-md-6 nopadding">
<input type="text" id="filter" ng-model="state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
<input type="text" id="filter-users" ng-model="state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
@ -123,7 +123,7 @@
>
</div>
<div class="col-sm-12 col-md-6 nopadding">
<input type="text" id="filter" ng-model="state.filterGroupMembers" placeholder="Filter..." class="form-control input-sm" />
<input type="text" id="filter-group" ng-model="state.filterGroupMembers" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">