portainer/app/kubernetes/views/applications/edit/application.html

534 lines
27 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>
<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'"></pr-icon> {{ ctrl.application.ApplicationOwner }}
</span>
<span class="vertical-center"> <pr-icon icon="'clock'" mode="'alt'"></pr-icon> {{ ctrl.application.CreationDate | getisodate }} </span>
<span ng-if="ctrl.application.ApplicationOwner" data-cy="k8sAppDetail-creationMethod" class="vertical-center">
<pr-icon icon="'clock'"></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'"></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'" ng-if="ctrl.state.expandedNote"></pr-icon>
<pr-icon icon="'chevron-down'" 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="'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="'code'" class="mr-1"></pr-icon>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="'code'" class-name="'mr-1'"></pr-icon>Edit External application
</button>
<be-only-button
icon="'refresh-cw'"
feature-id="ctrl.limitedFeature"
message="'A rolling restart of the application is performed.'"
heading="'Rolling restart'"
button-text="'Rolling restart'"
></be-only-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"
namespaces="ctrl.allNamespaces"
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>