mirror of https://github.com/portainer/portainer
409 lines
20 KiB
HTML
409 lines
20 KiB
HTML
<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="'svg-laptopcode'" class-name="'mr-1'"></pr-icon> Application </uib-tab-heading>
|
|
<application-summary-widget></application-summary-widget>
|
|
</uib-tab>
|
|
|
|
<uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)">
|
|
<uib-tab-heading>
|
|
<pr-icon icon="'minimize-2'"></pr-icon> Placement
|
|
<div ng-if="ctrl.state.placementWarning" class="vertical-center">
|
|
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
|
|
warning
|
|
</div>
|
|
</uib-tab-heading>
|
|
<div class="small text-muted vertical-center" style="padding: 20px">
|
|
<pr-icon icon="'info'" mode="'primary'"></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="minimize-2"
|
|
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="'history'"></pr-icon> Events
|
|
<div ng-if="ctrl.hasEventWarnings()" class="vertical-center">
|
|
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
|
|
{{ ctrl.state.eventWarningCount }} warning(s)
|
|
</div>
|
|
</uib-tab-heading>
|
|
<kubernetes-events-datatable
|
|
title-text="Events"
|
|
title-icon="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'"></pr-icon> YAML </uib-tab-heading>
|
|
<div class="px-5" 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()" class="mb-4 flex items-center gap-1">
|
|
<button
|
|
ng-if="!ctrl.isExternalApplication()"
|
|
type="button"
|
|
class="btn btn-sm btn-light vertical-center ml-2"
|
|
ui-sref="kubernetes.applications.application.edit"
|
|
style="margin-left: 0"
|
|
data-cy="k8sAppDetail-editAppButton"
|
|
>
|
|
<pr-icon icon="'pencil'" class="mr-1"></pr-icon>{{ ctrl.stack.IsComposeFormat ? 'View this application' : 'Edit this application' }}
|
|
</button>
|
|
<button
|
|
authorization="K8sApplicationDetailsW"
|
|
ng-if="ctrl.isExternalApplication()"
|
|
type="button"
|
|
class="btn btn-sm btn-light ml-2"
|
|
ui-sref="kubernetes.applications.application.edit"
|
|
data-cy="k8sAppDetail-editAppButton"
|
|
>
|
|
<pr-icon icon="'pencil'" class-name="'mr-1'"></pr-icon>Edit external application
|
|
</button>
|
|
<be-teaser-button
|
|
icon="'refresh-cw'"
|
|
feature-id="ctrl.limitedFeature"
|
|
message="'A rolling restart of the application is performed.'"
|
|
heading="'Rolling restart'"
|
|
button-text="'Rolling restart'"
|
|
class-name="'be-tooltip-teaser'"
|
|
className="'be-tooltip-teaser'"
|
|
></be-teaser-button>
|
|
<button
|
|
ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD"
|
|
type="button"
|
|
class="btn btn-sm btn-light ml-2"
|
|
ng-click="ctrl.redeployApplication()"
|
|
data-cy="k8sAppDetail-redeployButton"
|
|
>
|
|
<pr-icon icon="'rotate-cw'" class="'mr-1'"></pr-icon>Redeploy
|
|
</button>
|
|
<button
|
|
ng-if="!ctrl.isExternalApplication()"
|
|
type="button"
|
|
class="btn btn-sm btn-light"
|
|
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'" 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"></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"></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="'info'" mode="'primary'" class="mr-1"></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="'info'" mode="'primary'" class="mr-1"></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"></pr-icon>Auto-scaling </div>
|
|
|
|
<div class="small text-muted" ng-if="!ctrl.application.AutoScaler" style="margin-bottom: 15px">
|
|
<pr-icon icon="'info'" mode="'primary'" class="mr-1"></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"></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="'info'" mode="'primary'" class="mr-1"></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="'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"></pr-icon>{{ envvar.valueFrom.configMapKeyRef.key }}</span
|
|
>
|
|
<span ng-if="envvar.valueFrom.secretKeyRef" data-cy="k8sAppDetail-envVarValue"
|
|
><pr-icon icon="'key'" class="mr-1"></pr-icon>{{ envvar.valueFrom.secretKeyRef.key }}</span
|
|
>
|
|
<span ng-if="envvar.valueFrom.fieldRef" data-cy="k8sAppDetail-envVarValue"
|
|
><pr-icon icon="'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"></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"></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="'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" 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"></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"></pr-icon>
|
|
Data persistence
|
|
</div>
|
|
|
|
<div class="small text-muted" ng-if="!ctrl.hasPersistedFolders()">
|
|
<pr-icon icon="'info'" mode="'primary'" class="mr-1"></pr-icon>
|
|
This application has no persisted folders.
|
|
</div>
|
|
|
|
<div ng-if="ctrl.hasPersistedFolders()">
|
|
<div class="small text-muted vertical-center" style="margin-bottom: 15px">
|
|
Data access policy:
|
|
<pr-icon icon="ctrl.application.DataAccessPolicy | kubernetesApplicationDataAccessPolicyIcon"></pr-icon>
|
|
{{ 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
|
|
class="hyperlink"
|
|
ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName, namespace: ctrl.application.ResourcePool })"
|
|
data-cy="k8sAppDetail-volClaimName"
|
|
><pr-icon icon="'database'" class="mr-1"></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="'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
|
|
class="hyperlink"
|
|
ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName + '-' + container.PodName, namespace: ctrl.application.ResourcePool })"
|
|
>
|
|
<pr-icon icon="'database'" class="mr-1"></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>
|