<page-header ng-if="ctrl.state.viewReady" title="'Application details'" breadcrumbs="[ { label:'Namespaces', link:'kubernetes.resourcePools' }, { label:ctrl.application.ResourcePool, link: 'kubernetes.resourcePools.resourcePool', linkParams:{ id: ctrl.application.ResourcePool } }, { label:'Applications', link:'kubernetes.applications' }, ctrl.application.Name ]" reload="true" > </page-header> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <div ng-if="ctrl.state.viewReady"> <div class="row kubernetes-application"> <div class="col-sm-12"> <rd-widget> <rd-widget-body classes="no-padding"> <uib-tabset active="ctrl.state.activeTab" justified="true" type="pills"> <uib-tab index="0" classes="btn-sm" select="ctrl.selectTab(0)"> <uib-tab-heading> <pr-icon icon="'fa-laptop-code'" class-name="'mr-1'"></pr-icon> Application </uib-tab-heading> <div style="padding: 20px"> <table class="table"> <tbody> <tr> <td>Name</td> <td data-cy="k8sAppDetail-appName"> {{ ctrl.application.Name }} <span class="label label-primary image-tag label-margins" ng-if="!ctrl.isSystemNamespace() && ctrl.isExternalApplication()">external</span> </td> </tr> <tr> <td>Stack</td> <td data-cy="k8sAppDetail-stackName">{{ ctrl.application.StackName || '-' }}</td> </tr> <tr> <td>Namespace</td> <td data-cy="k8sAppDetail-resourcePoolName"> <a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a> <span style="margin-left: 5px" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace()">system</span> </td> </tr> <tr> <td>Application Type</td> <td data-cy="k8sAppDetail-appType"> {{ ctrl.application.ApplicationType | kubernetesApplicationTypeText }} </td> </tr> <tr> <td>Status</td> <td ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD"> <span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.REPLICATED" data-cy="k8sAppDetail-deployType">Replicated</span> <span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.GLOBAL" data-cy="k8sAppDetail-appType">Global</span> <code data-cy="k8sAppDetail-runningPods">{{ ctrl.application.RunningPodsCount }}</code> / <code data-cy="k8sAppDetail-totalPods">{{ ctrl.application.TotalPodsCount }}</code> </td> <td ng-if="ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD"> {{ ctrl.application.Pods[0].Status }} </td> </tr> <tr ng-if="ctrl.application.Requests.Cpu || ctrl.application.Requests.Memory"> <td> <div>Resource reservations</div> <div ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD" class="text-muted small"> per instance </div> </td> <td> <div ng-if="ctrl.application.Requests.Cpu" data-cy="k8sAppDetail-cpuReservation" >CPU {{ ctrl.application.Requests.Cpu | kubernetesApplicationCPUValue }}</div > <div ng-if="ctrl.application.Requests.Memory" data-cy="k8sAppDetail-memoryReservation">Memory {{ ctrl.application.Requests.Memory | humansize }}</div> </td> </tr> <tr> <td>Creation</td> <td> <span ng-if="ctrl.application.ApplicationOwner" class="mr-1 vertical-center" data-cy="k8sAppDetail-owner"> <pr-icon icon="'user'" feather="true"></pr-icon> {{ ctrl.application.ApplicationOwner }} </span> <span class="vertical-center"> <pr-icon icon="'clock'" mode="'alt'" feather="true"></pr-icon> {{ ctrl.application.CreationDate | getisodate }} </span> <span ng-if="ctrl.application.ApplicationOwner" data-cy="k8sAppDetail-creationMethod" class="vertical-center"> <pr-icon icon="'clock'" feather="true"></pr-icon> Deployed from {{ ctrl.state.appType }} </span> </td> </tr> <tr> <td colspan="2"> <form class="form-horizontal" name="kubernetesApplicationNoteForm"> <div class="form-group"> <div class="col-sm-12 vertical-center"> <pr-icon icon="'edit'" feather="true"></pr-icon> Note <button class="btn btn-xs btn-light vertical-center" ng-click="ctrl.state.expandedNote = !ctrl.state.expandedNote;" data-cy="k8sAppDetail-expandNoteButton" >{{ ctrl.state.expandedNote ? 'Collapse' : 'Expand' }} <pr-icon icon="'chevron-up'" feather="true" ng-if="ctrl.state.expandedNote"></pr-icon> <pr-icon icon="'chevron-down'" feather="true" ng-if="!ctrl.state.expandedNote"></pr-icon> </button> </div> </div> <div class="form-group" ng-if="ctrl.state.expandedNote"> <div class="col-sm-12"> <textarea class="form-control" name="application_note" id="application_note" ng-model="ctrl.formValues.Note" rows="5" placeholder="Enter a note about this application..." ></textarea> </div> </div> <div class="form-group" ng-if="ctrl.state.expandedNote"> <div class="col-sm-12"> <button class="btn btn-primary btn-sm" style="margin-left: 0px" type="button" ng-click="ctrl.updateApplication()" ng-disabled="ctrl.formValues.Note === ctrl.application.Note" data-cy="k8sAppDetail-saveNoteButton" >{{ ctrl.application.Note ? 'Update' : 'Save' }} note</button > </div> </div> </form> </td> </tr> <!-- <tr> <td colspan="2"> <form class="form-horizontal" name="KubernetesApplicationRollbackForm"> <div class="form-group"> <label for="resource-pool-selector" class="col-sm-2 col-lg-1 control-label text-left">Version</label> <div class="col-sm-2"> <select class="form-control" id="resource-pool-selector" ng-model="ctrl.formValues.SelectedRevision" ng-options="revision as revision.revision for revision in ctrl.application.Revisions"></select> </div> <div class="col-sm-2"> <button class="btn btn-primary btn-sm" style="margin-left: 0px;" type="button" ng-click="ctrl.rollbackApplication()" ng-disabled="ctrl.formValues.SelectedRevision.revision === ctrl.application.CurrentRevision.revision">Rollback</button> </div> </div> </form> </td> </tr> --> </tbody> </table> </div> </uib-tab> <uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)"> <uib-tab-heading> <pr-icon icon="'fa-compress-arrows-alt'"></pr-icon> Placement <div ng-if="ctrl.state.placementWarning" class="vertical-center"> <pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon> warning </div> </uib-tab-heading> <div class="small text-muted vertical-center" style="padding: 20px"> <pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon> The placement component helps you understand whether or not this application can be deployed on a specific node. </div> <kubernetes-application-placements-datatable title-text="Placement constraints/preferences" title-icon="fa-compress-arrows-alt" dataset="ctrl.placements" table-key="kubernetes.application.placements" order-by="Name" reverse-order="false" loading="ctrl.state.dataLoading" refresh-callback="ctrl.getApplication" ></kubernetes-application-placements-datatable> </uib-tab> <uib-tab index="2" classes="btn-sm" select="ctrl.selectTab(2)"> <uib-tab-heading> <pr-icon icon="'fa-history'"></pr-icon> Events <div ng-if="ctrl.hasEventWarnings()" class="vertical-center"> <pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon> {{ ctrl.state.eventWarningCount }} warning(s) </div> </uib-tab-heading> <kubernetes-events-datatable title-text="Events" title-icon="fa-history" dataset="ctrl.events" table-key="kubernetes.application.events" order-by="Date" reverse-order="true" loading="ctrl.state.eventsLoading" refresh-callback="ctrl.getEvents" ></kubernetes-events-datatable> </uib-tab> <uib-tab index="3" ng-if="ctrl.application.Yaml" select="ctrl.showEditor()" classes="btn-sm"> <uib-tab-heading> <pr-icon icon="'code'" feather="true"></pr-icon> YAML </uib-tab-heading> <div style="padding-right: 25px" ng-if="ctrl.state.showEditorTab"> <kubernetes-yaml-inspector key="application-yaml" data="ctrl.application.Yaml"></kubernetes-yaml-inspector> </div> </uib-tab> </uib-tabset> </rd-widget-body> </rd-widget> </div> </div> <div class="row"> <div class="col-sm-12"> <rd-widget> <rd-widget-body> <div ng-if="!ctrl.isSystemNamespace()" style="margin-bottom: 15px"> <button ng-if="!ctrl.isExternalApplication()" type="button" class="btn btn-sm btn-light vertical-center" ui-sref="kubernetes.applications.application.edit" style="margin-left: 0" data-cy="k8sAppDetail-editAppButton" > <pr-icon icon="'code'" class="mr-1" feather="true"></pr-icon>Edit this application </button> <button authorization="K8sApplicationDetailsW" ng-if="ctrl.isExternalApplication()" type="button" class="btn btn-sm btn-light" ui-sref="kubernetes.applications.application.edit" style="margin-left: 0" data-cy="k8sAppDetail-editAppButton" > <pr-icon icon="'code'" class-name="'mr-1'" feather="true"></pr-icon>Edit External application </button> <button ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD" type="button" class="btn btn-sm btn-light" style="margin-left: 0" ng-click="ctrl.redeployApplication()" data-cy="k8sAppDetail-redeployButton" > <pr-icon icon="'rotate-cw'" class="'mr-1'" feather="true"></pr-icon>Redeploy </button> <button ng-if="!ctrl.isExternalApplication()" type="button" class="btn btn-sm btn-light" style="margin-left: 0" ng-click="ctrl.rollbackApplication()" ng-disabled="ctrl.application.Revisions.length < 2 || ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.APPLICATION_FORM" data-cy="k8sAppDetail-rollbackButton" > <pr-icon icon="'rotate-ccw'" feather="true" class="mr-1"></pr-icon>Rollback to previous configuration </button> <a ng-if="ctrl.isStack() && ctrl.stackFileContent" class="btn btn-sm btn-primary space-left" ui-sref="kubernetes.templates.custom.new({fileContent: ctrl.stackFileContent})" > <pr-icon icon="'plus'" class="mr-1" feather="true"></pr-icon>Create template from application </a> </div> <!-- ACCESSING APPLICATION --> <div class="text-muted" style="margin-bottom: 15px"> <pr-icon icon="'external-link'" class="mr-1" feather="true"></pr-icon>Accessing the application </div> <div class="small text-muted" ng-if="ctrl.application.PublishedPorts.length === 0" style="margin-bottom: 15px"> <pr-icon icon="'alert-circle'" mode="'primary'" class="mr-1" feather="true"></pr-icon>This application is not exposing any port. </div> <div ng-if="ctrl.application.Services.length !== 0"> <!-- Services notice --> <div> <div class="small text-muted"> <p> <pr-icon icon="'alert-circle'" mode="'primary'" class="mr-1" feather="true"></pr-icon>This application is exposed through service(s) as below: </p> </div> </div> <!-- table --> <kubernetes-application-services-table services="ctrl.application.Services" application="ctrl.application" public-url="ctrl.state.publicUrl" ></kubernetes-application-services-table> <!-- table --> <!-- table --> <kubernetes-application-ingress-table application="ctrl.application" public-url="ctrl.state.publicUrl"></kubernetes-application-ingress-table> <!-- table --> </div> <!-- !ACCESSING APPLICATION --> <!-- AUTO SCALING --> <div class="text-muted" style="margin-bottom: 15px"> <pr-icon icon="'move'" class="mr-1" feather="true"></pr-icon>Auto-scaling </div> <div class="small text-muted" ng-if="!ctrl.application.AutoScaler" style="margin-bottom: 15px"> <pr-icon icon="'alert-circle'" mode="'primary'" class="mr-1" feather="true"></pr-icon> This application does not have an autoscaling policy defined. </div> <div ng-if="ctrl.application.AutoScaler"> <div style="margin-top: 15px; width: 50%"> <table class="table"> <tbody> <tr class="text-muted"> <td style="width: 33%">Minimum instances</td> <td style="width: 33%">Maximum instances</td> <td style="width: 33%"> Target CPU usage <portainer-tooltip message="'The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.'"> </portainer-tooltip> </td> </tr> <tr> <td data-cy="k8sAppDetail-minReplicas">{{ ctrl.application.AutoScaler.MinReplicas }}</td> <td data-cy="k8sAppDetail-maxReplicas">{{ ctrl.application.AutoScaler.MaxReplicas }}</td> <td data-cy="k8sAppDetail-targetCPU">{{ ctrl.application.AutoScaler.TargetCPUUtilization }}%</td> </tr> </tbody> </table> </div> </div> <!-- !AUTO SCALING --> <!-- CONFIGURATIONS --> <div class="text-muted" style="margin-bottom: 15px; margin-top: 25px"> <pr-icon icon="'file'" class="mr-1" feather="true"></pr-icon> Configuration </div> <div class="small text-muted" ng-if="!ctrl.application.Env.length > 0 && !ctrl.hasVolumeConfiguration()" style="margin-bottom: 15px"> <pr-icon icon="'alert-circle'" mode="'primary'" class="mr-1" feather="true"></pr-icon> This application is not using any environment variable or configuration. </div> <table class="table" ng-if="ctrl.application.Env.length > 0"> <tr class="text-muted"> <td style="width: 25%">Container</td> <td style="width: 25%">Environment variable</td> <td style="width: 25%">Value</td> <td style="width: 25%">Configuration</td> </tr> <tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0"> <tr ng-repeat="envvar in container.Env | orderBy: 'name'"> <td data-cy="k8sAppDetail-containerName"> {{ container.Name }} <span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT" ><pr-icon icon="'fa-asterisk'"></pr-icon> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" target="_blank" >init container</a >)</span > </td> <td data-cy="k8sAppDetail-envVarName">{{ envvar.name }}</td> <td> <span ng-if="envvar.value" data-cy="k8sAppDetail-envVarValue">{{ envvar.value }}</span> <span ng-if="envvar.valueFrom.configMapKeyRef" data-cy="k8sAppDetail-envVarValue" ><pr-icon icon="'key'" class="mr-1" feather="true"></pr-icon>{{ envvar.valueFrom.configMapKeyRef.key }}</span > <span ng-if="envvar.valueFrom.secretKeyRef" data-cy="k8sAppDetail-envVarValue" ><pr-icon icon="'key'" class="mr-1" feather="true"></pr-icon>{{ envvar.valueFrom.secretKeyRef.key }}</span > <span ng-if="envvar.valueFrom.fieldRef" data-cy="k8sAppDetail-envVarValue" ><pr-icon icon="'fa-asterisk'"></pr-icon> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#capabilities-of-the-downward-api" target="_blank" >downward API</a >)</span > <span ng-if="!envvar.value && !envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef && !envvar.valueFrom.fieldRef">-</span> </td> <td> <span ng-if="envvar.value || envvar.valueFrom.fieldRef || (!envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef)">-</span> <span ng-if="envvar.valueFrom.configMapKeyRef" data-cy="k8sAppDetail-configName" ><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.configMapKeyRef.name, namespace: ctrl.application.ResourcePool })" ><pr-icon icon="'file'" class="mr-1" feather="true"></pr-icon>{{ envvar.valueFrom.configMapKeyRef.name }}</a ></span > <span ng-if="envvar.valueFrom.secretKeyRef" data-cy="k8sAppDetail-configName" ><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.secretKeyRef.name, namespace: ctrl.application.ResourcePool })" ><pr-icon icon="'file'" class="mr-1" feather="true"></pr-icon>{{ envvar.valueFrom.secretKeyRef.name }}</a ></span > </td> </tr> </tbody> </table> <table class="table" ng-if="ctrl.hasVolumeConfiguration()"> <tr class="text-muted"> <td style="width: 25%">Container</td> <td style="width: 25%">Configuration path</td> <td style="width: 25%">Value</td> <td style="width: 25%">Configuration</td> </tr> <tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0"> <tr ng-repeat="volume in container.ConfigurationVolumes track by $index" style="border-top: 0"> <td> {{ container.Name }} <span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT" ><pr-icon icon="'fa-asterisk'"></pr-icon> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" target="_blank" >init container</a >)</span > </td> <td> {{ volume.fileMountPath }} </td> <td> <pr-icon icon="'plus'" class="mr-1" feather="true" ng-if="volume.configurationKey"></pr-icon> {{ volume.configurationKey ? volume.configurationKey : '-' }} </td> <td> <a ui-sref="kubernetes.configurations.configuration({ name: volume.configurationName, namespace: ctrl.application.ResourcePool })" ><pr-icon icon="'plus'" class="mr-1" feather="true"></pr-icon>{{ volume.configurationName }}</a > </td> </tr> </tbody> </table> <!-- !CONFIGURATIONS --> <!-- DATA PERSISTENCE --> <div class="text-muted" style="margin-bottom: 15px; margin-top: 25px"> <pr-icon icon="'database'" class="mr-1" feather="true"></pr-icon> Data persistence </div> <div class="small text-muted" ng-if="!ctrl.hasPersistedFolders()"> <pr-icon icon="'alert-circle'" mode="'primary'" class="mr-1" feather="true"></pr-icon> This application has no persisted folders. </div> <div ng-if="ctrl.hasPersistedFolders()"> <div class="small text-muted" style="margin-bottom: 15px"> Data access policy: <i class="fa {{ ctrl.application.DataAccessPolicy | kubernetesApplicationDataAccessPolicyIcon }}" aria-hidden="true"></i> {{ ctrl.application.DataAccessPolicy | kubernetesApplicationDataAccessPolicyText }} <portainer-tooltip position="'right'" message="ctrl.application.DataAccessPolicy | kubernetesApplicationDataAccessPolicyTooltip"> </portainer-tooltip> </div> <table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED"> <tr class="text-muted"> <td style="width: 33%">Persisted folder</td> <td style="width: 66%">Persistence</td> </tr> <tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0"> <tr ng-repeat="volume in container.PersistedFolders track by $index"> <td data-cy="k8sAppDetail-volMountPath"> {{ volume.MountPath }} </td> <td ng-if="volume.PersistentVolumeClaimName"> <a ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName, namespace: ctrl.application.ResourcePool })" data-cy="k8sAppDetail-volClaimName" ><pr-icon icon="'database'" class="mr-1" feather="true"></pr-icon>{{ volume.PersistentVolumeClaimName }}</a > </td> <td ng-if="volume.HostPath"> {{ volume.HostPath }} on host filesystem </td> </tr> </tbody> </table> <table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED"> <thead> <tr class="text-muted"> <td style="width: 25%">Container name</td> <td style="width: 25%">Pod name</td> <td style="width: 25%">Persisted folder</td> <td style="width: 25%">Persistence</td> </tr> </thead> <tbody ng-repeat="container in ctrl.allContainers track by $index" style="border-top: none"> <tr ng-repeat="volume in container.PersistedFolders track by $index"> <td> {{ container.Name }} <span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT" ><pr-icon icon="'fa-asterisk'"></pr-icon> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" target="_blank" >init container</a >)</span > </td> <td>{{ container.PodName }}</td> <td> {{ volume.MountPath }} </td> <td ng-if="volume.PersistentVolumeClaimName"> <a ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName + '-' + container.PodName, namespace: ctrl.application.ResourcePool })"> <pr-icon icon="'database'" class="mr-1" feather="true"></pr-icon>{{ volume.PersistentVolumeClaimName + '-' + container.PodName }}</a > </td> <td ng-if="volume.HostPath"> {{ volume.HostPath }} on host filesystem </td> </tr> </tbody> </table> <!-- !DATA PERSISTENCE --> </div> </rd-widget-body> </rd-widget> </div> </div> <div class="row"> <div class="col-sm-12"> <kubernetes-containers-datatable title-text="Application containers" title-icon="server" dataset="ctrl.allContainers" table-key="kubernetes.application.containers" is-pod="ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD" order-by="{{ ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD ? 'Name' : 'PodName' }}" use-server-metrics="ctrl.state.useServerMetrics" > </kubernetes-containers-datatable> </div> </div> </div>