Compare commits

...

6 Commits

18 changed files with 112 additions and 77 deletions

View File

@@ -94,6 +94,7 @@ body:
multiple: false
options:
- '2.22.0'
- '2.21.3'
- '2.21.2'
- '2.21.1'
- '2.21.0'

View File

@@ -2,6 +2,7 @@ package edgestacks
import (
"errors"
"fmt"
"net/http"
"time"
@@ -63,7 +64,7 @@ func (handler *Handler) edgeStackStatusUpdate(w http.ResponseWriter, r *http.Req
var payload updateStatusPayload
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
return httperror.BadRequest("Invalid request payload", err)
return httperror.BadRequest("Invalid request payload", fmt.Errorf("edge polling error: %w. Environment ID: %d", err, payload.EndpointID))
}
var stack *portainer.EdgeStack
@@ -95,16 +96,16 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
return nil, nil
}
return nil, err
return nil, fmt.Errorf("unable to retrieve Edge stack from the database: %w. Environment ID: %d", err, payload.EndpointID)
}
endpoint, err := tx.Endpoint().Endpoint(payload.EndpointID)
if err != nil {
return nil, handler.handlerDBErr(err, "Unable to find an environment with the specified identifier inside the database")
return nil, handler.handlerDBErr(fmt.Errorf("unable to find the environment from the database: %w. Environment ID: %d", err, payload.EndpointID), "unable to find the environment")
}
if err := handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint); err != nil {
return nil, httperror.Forbidden("Permission denied to access environment", err)
return nil, httperror.Forbidden("Permission denied to access environment", fmt.Errorf("unauthorized edge endpoint operation: %w. Environment name: %s", err, endpoint.Name))
}
status := *payload.Status
@@ -123,7 +124,7 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
updateEnvStatus(payload.EndpointID, stack, deploymentStatus)
if err := tx.EdgeStack().UpdateEdgeStack(stackID, stack); err != nil {
return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database")
return nil, handler.handlerDBErr(fmt.Errorf("unable to update Edge stack to the database: %w. Environment name: %s", err, endpoint.Name), "unable to update Edge stack")
}
return stack, nil

View File

@@ -2,6 +2,7 @@ package endpointedge
import (
"errors"
"fmt"
"net/http"
"strconv"
@@ -39,32 +40,30 @@ func (handler *Handler) endpointEdgeJobsLogs(w http.ResponseWriter, r *http.Requ
return httperror.BadRequest("Unable to find an environment on request context", err)
}
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err)
if err := handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint); err != nil {
return httperror.Forbidden("Permission denied to access environment", fmt.Errorf("unauthorized edge endpoint operation: %w. Environment name: %s", err, endpoint.Name))
}
edgeJobID, err := request.RetrieveNumericRouteVariableValue(r, "jobID")
if err != nil {
return httperror.BadRequest("Invalid edge job identifier route variable", err)
return httperror.BadRequest("Invalid edge job identifier route variable", fmt.Errorf("invalid Edge job route variable: %w. Environment name: %s", err, endpoint.Name))
}
var payload logsPayload
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return httperror.BadRequest("Invalid request payload", err)
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
return httperror.BadRequest("Invalid request payload", fmt.Errorf("invalid Edge job request payload: %w. Environment name: %s", err, endpoint.Name))
}
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
return handler.getEdgeJobLobs(tx, endpoint.ID, portainer.EdgeJobID(edgeJobID), payload)
})
if err != nil {
}); err != nil {
var httpErr *httperror.HandlerError
if errors.As(err, &httpErr) {
httpErr.Err = fmt.Errorf("edge polling error: %w. Environment name: %s", httpErr.Err, endpoint.Name)
return httpErr
}
return httperror.InternalServerError("Unexpected error", err)
return httperror.InternalServerError("Unexpected error", fmt.Errorf("edge polling error: %w. Environment name: %s", err, endpoint.Name))
}
return response.JSON(w, nil)

View File

@@ -1,7 +1,7 @@
package endpointedge
import (
"errors"
"fmt"
"net/http"
portainer "github.com/portainer/portainer/api"
@@ -33,27 +33,26 @@ func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http.
return httperror.BadRequest("Unable to find an environment on request context", err)
}
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err)
if err := handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint); err != nil {
return httperror.Forbidden("Permission denied to access environment", fmt.Errorf("unauthorized edge endpoint operation: %w. Environment name: %s", err, endpoint.Name))
}
edgeStackID, err := request.RetrieveNumericRouteVariableValue(r, "stackId")
if err != nil {
return httperror.BadRequest("Invalid edge stack identifier route variable", err)
return httperror.BadRequest("Invalid edge stack identifier route variable", fmt.Errorf("invalid Edge stack route variable: %w. Environment name: %s", err, endpoint.Name))
}
edgeStack, err := handler.DataStore.EdgeStack().EdgeStack(portainer.EdgeStackID(edgeStackID))
if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound("Unable to find an edge stack with the specified identifier inside the database", err)
return httperror.NotFound("Unable to find an edge stack with the specified identifier inside the database", fmt.Errorf("unable to find the Edge stack from database: %w. Environment name: %s", err, endpoint.Name))
} else if err != nil {
return httperror.InternalServerError("Unable to find an edge stack with the specified identifier inside the database", err)
return httperror.InternalServerError("Unable to find an edge stack with the specified identifier inside the database", fmt.Errorf("failed to find the Edge stack from database: %w. Environment name: %s", err, endpoint.Name))
}
fileName := edgeStack.EntryPoint
if endpointutils.IsDockerEndpoint(endpoint) {
if fileName == "" {
return httperror.BadRequest("Docker is not supported by this stack", errors.New("Docker is not supported by this stack"))
return httperror.BadRequest("Docker is not supported by this stack", fmt.Errorf("no filename is provided for the Docker endpoint. Environment name: %s", endpoint.Name))
}
}
@@ -66,18 +65,18 @@ func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http.
fileName = edgeStack.ManifestPath
if fileName == "" {
return httperror.BadRequest("Kubernetes is not supported by this stack", errors.New("Kubernetes is not supported by this stack"))
return httperror.BadRequest("Kubernetes is not supported by this stack", fmt.Errorf("no filename is provided for the Kubernetes endpoint. Environment name: %s", endpoint.Name))
}
}
dirEntries, err := filesystem.LoadDir(edgeStack.ProjectPath)
if err != nil {
return httperror.InternalServerError("Unable to load repository", err)
return httperror.InternalServerError("Unable to load repository", fmt.Errorf("failed to load project directory: %w. Environment name: %s", err, endpoint.Name))
}
fileContent, err := filesystem.FilterDirForCompatibility(dirEntries, fileName, endpoint.Agent.Version)
if err != nil {
return httperror.InternalServerError("File not found", err)
return httperror.InternalServerError("File not found", fmt.Errorf("unable to find file: %w. Environment name: %s", err, endpoint.Name))
}
dirEntries = filesystem.FilterDirForEntryFile(dirEntries, fileName)

View File

@@ -85,25 +85,25 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http
if _, ok := handler.DataStore.Endpoint().Heartbeat(portainer.EndpointID(endpointID)); !ok {
// EE-5190
return httperror.Forbidden("Permission denied to access environment", errors.New("the device has not been trusted yet"))
return httperror.Forbidden("Permission denied to access environment. The device has not been trusted yet", fmt.Errorf("unable to retrieve endpoint heartbeat. Environment ID: %d", endpointID))
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err != nil {
// EE-5190
return httperror.Forbidden("Permission denied to access environment", errors.New("the device has not been trusted yet"))
return httperror.Forbidden("Permission denied to access environment. The device has not been trusted yet", fmt.Errorf("unable to retrieve endpoint from database: %w. Environment ID: %d", err, endpointID))
}
firstConn := endpoint.LastCheckInDate == 0
if err := handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint); err != nil {
return httperror.Forbidden("Permission denied to access environment", err)
return httperror.Forbidden("Permission denied to access environment. The device has not been trusted yet", fmt.Errorf("unauthorized Edge endpoint operation: %w. Environment name: %s", err, endpoint.Name))
}
handler.DataStore.Endpoint().UpdateHeartbeat(endpoint.ID)
if err := handler.requestBouncer.TrustedEdgeEnvironmentAccess(handler.DataStore, endpoint); err != nil {
return httperror.Forbidden("Permission denied to access environment", err)
return httperror.Forbidden("Permission denied to access environment. The device has not been trusted yet", fmt.Errorf("untrusted Edge environment access: %w. Environment name: %s", err, endpoint.Name))
}
var statusResponse *endpointEdgeStatusInspectResponse
@@ -113,10 +113,11 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http
}); err != nil {
var httpErr *httperror.HandlerError
if errors.As(err, &httpErr) {
httpErr.Err = fmt.Errorf("edge polling error: %w. Environment name: %s", httpErr.Err, endpoint.Name)
return httpErr
}
return httperror.InternalServerError("Unexpected error", err)
return httperror.InternalServerError("Unexpected error", fmt.Errorf("edge polling error: %w. Environment name: %s", err, endpoint.Name))
}
return cacheResponse(w, endpoint.ID, *statusResponse)

View File

@@ -70,8 +70,8 @@ type TLSInfo struct {
// Existing types
type K8sApplicationResource struct {
CPURequest int64 `json:"CpuRequest"`
CPULimit int64 `json:"CpuLimit"`
MemoryRequest int64 `json:"MemoryRequest"`
MemoryLimit int64 `json:"MemoryLimit"`
CPURequest float64 `json:"CpuRequest"`
CPULimit float64 `json:"CpuLimit"`
MemoryRequest int64 `json:"MemoryRequest"`
MemoryLimit int64 `json:"MemoryLimit"`
}

View File

@@ -135,8 +135,8 @@ func (kcl *KubeClient) GetApplicationsResource(namespace, node string) (models.K
for _, pod := range pods.Items {
for _, container := range pod.Spec.Containers {
resource.CPURequest += container.Resources.Requests.Cpu().MilliValue()
resource.CPULimit += container.Resources.Limits.Cpu().MilliValue()
resource.CPURequest += float64(container.Resources.Requests.Cpu().MilliValue())
resource.CPULimit += float64(container.Resources.Limits.Cpu().MilliValue())
resource.MemoryRequest += container.Resources.Requests.Memory().Value()
resource.MemoryLimit += container.Resources.Limits.Memory().Value()
}
@@ -356,8 +356,8 @@ func updateApplicationWithService(application models.K8sApplication, services []
func calculateResourceUsage(pod corev1.Pod) models.K8sApplicationResource {
resource := models.K8sApplicationResource{}
for _, container := range pod.Spec.Containers {
resource.CPURequest += container.Resources.Requests.Cpu().MilliValue()
resource.CPULimit += container.Resources.Limits.Cpu().MilliValue()
resource.CPURequest += float64(container.Resources.Requests.Cpu().MilliValue())
resource.CPULimit += float64(container.Resources.Limits.Cpu().MilliValue())
resource.MemoryRequest += container.Resources.Requests.Memory().Value()
resource.MemoryLimit += container.Resources.Limits.Memory().Value()
}

View File

@@ -214,6 +214,7 @@ func (kcl *KubeClient) CombineVolumesWithApplications(volumes *[]models.K8sVolum
hasReplicaSetOwnerReference := containsReplicaSetOwnerReference(pods)
replicaSetItems := make([]appsv1.ReplicaSet, 0)
deploymentItems := make([]appsv1.Deployment, 0)
if hasReplicaSetOwnerReference {
replicaSets, err := kcl.cli.AppsV1().ReplicaSets("").List(context.Background(), metav1.ListOptions{})
if err != nil {
@@ -221,19 +222,48 @@ func (kcl *KubeClient) CombineVolumesWithApplications(volumes *[]models.K8sVolum
return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list replica sets across the cluster. Error: %w", err)
}
replicaSetItems = replicaSets.Items
deployments, err := kcl.cli.AppsV1().Deployments("").List(context.Background(), metav1.ListOptions{})
if err != nil {
log.Error().Err(err).Msg("Failed to list deployments across the cluster")
return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list deployments across the cluster. Error: %w", err)
}
deploymentItems = deployments.Items
}
return kcl.updateVolumesWithOwningApplications(volumes, pods, replicaSetItems)
hasStatefulSetOwnerReference := containsStatefulSetOwnerReference(pods)
statefulSetItems := make([]appsv1.StatefulSet, 0)
if hasStatefulSetOwnerReference {
statefulSets, err := kcl.cli.AppsV1().StatefulSets("").List(context.Background(), metav1.ListOptions{})
if err != nil {
log.Error().Err(err).Msg("Failed to list stateful sets across the cluster")
return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list stateful sets across the cluster. Error: %w", err)
}
statefulSetItems = statefulSets.Items
}
hasDaemonSetOwnerReference := containsDaemonSetOwnerReference(pods)
daemonSetItems := make([]appsv1.DaemonSet, 0)
if hasDaemonSetOwnerReference {
daemonSets, err := kcl.cli.AppsV1().DaemonSets("").List(context.Background(), metav1.ListOptions{})
if err != nil {
log.Error().Err(err).Msg("Failed to list daemon sets across the cluster")
return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list daemon sets across the cluster. Error: %w", err)
}
daemonSetItems = daemonSets.Items
}
return kcl.updateVolumesWithOwningApplications(volumes, pods, deploymentItems, replicaSetItems, statefulSetItems, daemonSetItems)
}
// updateVolumesWithOwningApplications updates the volumes with the applications that use them.
func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8sVolumeInfo, pods *corev1.PodList, replicaSetItems []appsv1.ReplicaSet) (*[]models.K8sVolumeInfo, error) {
func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8sVolumeInfo, pods *corev1.PodList, deploymentItems []appsv1.Deployment, replicaSetItems []appsv1.ReplicaSet, statefulSetItems []appsv1.StatefulSet, daemonSetItems []appsv1.DaemonSet) (*[]models.K8sVolumeInfo, error) {
for i, volume := range *volumes {
for _, pod := range pods.Items {
if pod.Spec.Volumes != nil {
for _, podVolume := range pod.Spec.Volumes {
if podVolume.PersistentVolumeClaim != nil && podVolume.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace {
application, err := kcl.ConvertPodToApplication(pod, replicaSetItems, []appsv1.Deployment{}, []appsv1.StatefulSet{}, []appsv1.DaemonSet{}, []corev1.Service{}, false)
if podVolume.VolumeSource.PersistentVolumeClaim != nil && podVolume.VolumeSource.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace {
application, err := kcl.ConvertPodToApplication(pod, replicaSetItems, deploymentItems, statefulSetItems, daemonSetItems, []corev1.Service{}, false)
if err != nil {
log.Error().Err(err).Msg("Failed to convert pod to application")
return nil, fmt.Errorf("an error occurred during the CombineServicesWithApplications operation, unable to convert pod to application. Error: %w", err)

View File

@@ -31,19 +31,19 @@ func NewService() *Service {
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, error) {
token, err := getOAuthToken(code, configuration)
if err != nil {
log.Debug().Err(err).Msg("failed retrieving oauth token")
log.Error().Err(err).Msg("failed retrieving oauth token")
return "", err
}
idToken, err := getIdToken(token)
if err != nil {
log.Debug().Err(err).Msg("failed parsing id_token")
log.Error().Err(err).Msg("failed parsing id_token")
}
resource, err := getResource(token.AccessToken, configuration)
if err != nil {
log.Debug().Err(err).Msg("failed retrieving resource")
log.Error().Err(err).Msg("failed retrieving resource")
return "", err
}
@@ -52,7 +52,7 @@ func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings
username, err := getUsername(resource, configuration)
if err != nil {
log.Debug().Err(err).Msg("failed retrieving username")
log.Error().Err(err).Msg("failed retrieving username")
return "", err
}

View File

@@ -21,7 +21,7 @@ import {
KubernetesApplicationVolumePersistentPayload,
KubernetesApplicationVolumeSecretPayload,
} from 'Kubernetes/models/application/payloads';
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
import { generatedApplicationConfigVolumeName } from '@/react/kubernetes/volumes/utils';
import { HelmApplication } from 'Kubernetes/models/application/models';
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
import { KubernetesPodAffinity, KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
@@ -239,7 +239,7 @@ class KubernetesApplicationHelper {
const volKeys = _.filter(config.overridenKeys, (item) => item.type === 'FILESYSTEM');
const groupedVolKeys = _.groupBy(volKeys, 'path');
_.forEach(groupedVolKeys, (items, path) => {
const volumeName = KubernetesVolumeHelper.generatedApplicationConfigVolumeName(app.Name);
const volumeName = generatedApplicationConfigVolumeName(app.Name);
const configurationName = config.selectedConfiguration.metadata.name;
const itemsMap = _.map(items, (item) => {
const entry = new KubernetesApplicationVolumeEntryPayload();

View File

@@ -1,5 +1,4 @@
import _ from 'lodash-es';
import uuidv4 from 'uuid/v4';
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
class KubernetesVolumeHelper {
@@ -20,18 +19,6 @@ class KubernetesVolumeHelper {
);
});
}
static isUsed(item) {
return item.Applications.length !== 0;
}
static generatedApplicationConfigVolumeName(name) {
return 'config-' + name + '-' + uuidv4();
}
static isExternalVolume(volume) {
return !volume.PersistentVolumeClaim.ApplicationOwner;
}
}
export default KubernetesVolumeHelper;

View File

@@ -29,6 +29,7 @@ import { confirm, confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confi
import { buildConfirmButton } from '@@/modals/utils';
import { ModalType } from '@@/modals';
import { KUBE_STACK_NAME_VALIDATION_REGEX } from '@/react/kubernetes/DeployView/StackName/constants';
import { isVolumeUsed } from '@/react/kubernetes/volumes/utils';
class KubernetesCreateApplicationController {
/* #region CONSTRUCTOR */
@@ -785,7 +786,7 @@ class KubernetesCreateApplicationController {
});
this.volumes = volumes;
const filteredVolumes = _.filter(this.volumes, (volume) => {
const isUnused = !KubernetesVolumeHelper.isUsed(volume);
const isUnused = !isVolumeUsed(volume);
const isRWX = volume.PersistentVolumeClaim.storageClass && _.includes(volume.PersistentVolumeClaim.storageClass.AccessModes, 'RWX');
return isUnused || isRWX;
});

View File

@@ -310,7 +310,9 @@ class KubernetesNodeController {
}
getNodeUsage() {
return this.$async(this.getNodeUsageAsync);
if (this.hasResourceUsageAccess()) {
return this.$async(this.getNodeUsageAsync);
}
}
hasEventWarnings() {
@@ -361,9 +363,7 @@ class KubernetesNodeController {
useServerMetrics: this.endpoint.Kubernetes.Configuration.UseServerMetrics,
};
await this.getNodes();
await this.getEvents();
await this.getEndpoints();
await Promise.allSettled([this.getNodes(), this.getEvents(), this.getEndpoints(), this.getNodeUsage()]);
this.availableEffects = _.values(KubernetesNodeTaintEffects);
this.formValues = KubernetesNodeConverter.nodeToFormValues(this.node);

View File

@@ -6,6 +6,7 @@ import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
import { confirmRedeploy } from '@/react/kubernetes/volumes/ItemView/ConfirmRedeployModal';
import { isVolumeUsed, isVolumeExternal } from '@/react/kubernetes/volumes/utils';
class KubernetesVolumeController {
/* @ngInject */
@@ -49,7 +50,7 @@ class KubernetesVolumeController {
}
isExternalVolume() {
return KubernetesVolumeHelper.isExternalVolume(this.volume);
return isVolumeExternal(this.volume);
}
isSystemNamespace() {
@@ -57,7 +58,7 @@ class KubernetesVolumeController {
}
isUsed() {
return KubernetesVolumeHelper.isUsed(this.volume);
return isVolumeUsed(this.volume);
}
onChangeSize() {
@@ -102,7 +103,7 @@ class KubernetesVolumeController {
}
updateVolume() {
if (KubernetesVolumeHelper.isUsed(this.volume)) {
if (isVolumeUsed(this.volume)) {
confirmRedeploy().then((redeploy) => {
return this.$async(this.updateVolumeAsync, redeploy);
});

View File

@@ -139,7 +139,7 @@ function ErrorCell({
</div>
<div
className={clsx('overflow-hidden whitespace-normal', {
'h-[1.5em]': isExpanded,
'h-[1.5em]': !isExpanded,
})}
>
{value}

View File

@@ -1,7 +1,6 @@
import { Database } from 'lucide-react';
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
import KubernetesVolumeHelper from '@/kubernetes/helpers/volumeHelper';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { refreshableSettings } from '@@/datatables/types';
@@ -20,6 +19,7 @@ import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery'
import { useAllVolumesQuery } from '../queries/useVolumesQuery';
import { isSystemNamespace } from '../../namespaces/queries/useIsSystemNamespace';
import { useDeleteVolumes } from '../queries/useDeleteVolumes';
import { isVolumeUsed } from '../utils';
import { columns } from './columns';
@@ -68,7 +68,7 @@ export function VolumesDatatable() {
disableSelect={!hasWriteAuth}
isRowSelectable={({ original: volume }) =>
!isSystemNamespace(volume.ResourcePool.Namespace.Name, namespaces) &&
!KubernetesVolumeHelper.isUsed(volume)
!isVolumeUsed(volume)
}
renderTableActions={(selectedItems) => (
<Authorized authorizations="K8sVolumesW">

View File

@@ -1,6 +1,5 @@
import { CellContext } from '@tanstack/react-table';
import KubernetesVolumeHelper from '@/kubernetes/helpers/volumeHelper';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Link } from '@@/Link';
@@ -9,6 +8,7 @@ import { ExternalBadge } from '@@/Badge/ExternalBadge';
import { UnusedBadge } from '@@/Badge/UnusedBadge';
import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery';
import { isVolumeExternal, isVolumeUsed } from '../utils';
import { VolumeViewModel } from './types';
import { helper } from './columns.helper';
@@ -44,8 +44,8 @@ export function NameCell({
<SystemBadge />
) : (
<>
{KubernetesVolumeHelper.isExternalVolume(item) && <ExternalBadge />}
{!KubernetesVolumeHelper.isUsed(item) && <UnusedBadge />}
{isVolumeExternal(item) && <ExternalBadge />}
{!isVolumeUsed(item) && <UnusedBadge />}
</>
)}
</div>

View File

@@ -0,0 +1,15 @@
import uuidv4 from 'uuid/v4';
import { VolumeViewModel } from './ListView/types';
export function isVolumeUsed(volume: VolumeViewModel) {
return volume.Applications.length !== 0;
}
export function isVolumeExternal(volume: VolumeViewModel) {
return !volume.PersistentVolumeClaim.ApplicationOwner;
}
export function generatedApplicationConfigVolumeName(applicationName: string) {
return `config-${applicationName}-${uuidv4()}`;
}