mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 13:29:41 +02:00
refactor(app): summary widget migration [EE-5351] (#8796)
* refactor(app): summary widget migration [EE-5351] * update converter and limit display --------- Co-authored-by: testa113 <testa113>
This commit is contained in:
parent
745bbb7d79
commit
98e6393274
23 changed files with 964 additions and 304 deletions
|
@ -0,0 +1,289 @@
|
|||
import { User, Clock, Edit, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import moment from 'moment';
|
||||
import { useState } from 'react';
|
||||
import { Pod } from 'kubernetes-types/core/v1';
|
||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import { DetailsTable } from '@@/DetailsTable';
|
||||
import { Badge } from '@@/Badge';
|
||||
import { Link } from '@@/Link';
|
||||
import { Button, LoadingButton } from '@@/buttons';
|
||||
|
||||
import { isSystemNamespace } from '../../namespaces/utils';
|
||||
import {
|
||||
appStackNameLabel,
|
||||
appKindToDeploymentTypeMap,
|
||||
appOwnerLabel,
|
||||
appDeployMethodLabel,
|
||||
appNoteAnnotation,
|
||||
} from '../constants';
|
||||
import {
|
||||
applicationIsKind,
|
||||
bytesToReadableFormat,
|
||||
getResourceRequests,
|
||||
getRunningPods,
|
||||
getTotalPods,
|
||||
isExternalApplication,
|
||||
} from '../utils';
|
||||
import {
|
||||
useApplication,
|
||||
usePatchApplicationMutation,
|
||||
} from '../application.queries';
|
||||
import { Application } from '../types';
|
||||
|
||||
export function ApplicationSummaryWidget() {
|
||||
const stateAndParams = useCurrentStateAndParams();
|
||||
const {
|
||||
params: {
|
||||
namespace,
|
||||
name,
|
||||
'resource-type': resourceType,
|
||||
endpointId: environmentId,
|
||||
},
|
||||
} = stateAndParams;
|
||||
const applicationQuery = useApplication(
|
||||
environmentId,
|
||||
namespace,
|
||||
name,
|
||||
resourceType
|
||||
);
|
||||
const application = applicationQuery.data;
|
||||
const systemNamespace = isSystemNamespace(namespace);
|
||||
const externalApplication = application && isExternalApplication(application);
|
||||
const applicationRequests = application && getResourceRequests(application);
|
||||
const applicationOwner = application?.metadata?.labels?.[appOwnerLabel];
|
||||
const applicationDeployMethod = getApplicationDeployMethod(application);
|
||||
const applicationNote =
|
||||
application?.metadata?.annotations?.[appNoteAnnotation];
|
||||
|
||||
const [isNoteOpen, setIsNoteOpen] = useState(true);
|
||||
const [applicationNoteFormValues, setApplicationNoteFormValues] = useState(
|
||||
applicationNote || ''
|
||||
);
|
||||
const patchApplicationMutation = usePatchApplicationMutation(
|
||||
environmentId,
|
||||
namespace,
|
||||
name
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-5">
|
||||
<DetailsTable>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
<div
|
||||
className="flex items-center gap-x-2"
|
||||
data-cy="k8sAppDetail-appName"
|
||||
>
|
||||
{name}
|
||||
{externalApplication && !systemNamespace && (
|
||||
<Badge type="info">external</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stack</td>
|
||||
<td data-cy="k8sAppDetail-stackName">
|
||||
{application?.metadata?.labels?.[appStackNameLabel] || '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Namespace</td>
|
||||
<td>
|
||||
<div
|
||||
className="flex items-center gap-x-2"
|
||||
data-cy="k8sAppDetail-resourcePoolName"
|
||||
>
|
||||
<Link
|
||||
to="kubernetes.resourcePools.resourcePool"
|
||||
params={{ id: namespace }}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
{systemNamespace && <Badge type="info">system</Badge>}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Application type</td>
|
||||
<td data-cy="k8sAppDetail-appType">{application?.kind || '-'}</td>
|
||||
</tr>
|
||||
{application?.kind && (
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
{applicationIsKind<Pod>('Pod', application) && (
|
||||
<td data-cy="k8sAppDetail-appType">
|
||||
{application?.status?.phase}
|
||||
</td>
|
||||
)}
|
||||
{!applicationIsKind<Pod>('Pod', application) && (
|
||||
<td data-cy="k8sAppDetail-appType">
|
||||
{appKindToDeploymentTypeMap[application.kind]}
|
||||
<code className="ml-1">
|
||||
{getRunningPods(application)}
|
||||
</code> / <code>{getTotalPods(application)}</code>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
)}
|
||||
{(!!applicationRequests?.cpu || !!applicationRequests?.memoryBytes) && (
|
||||
<tr>
|
||||
<td>
|
||||
Resource reservations
|
||||
{!applicationIsKind<Pod>('Pod', application) && (
|
||||
<div className="text-muted small">per instance</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{!!applicationRequests?.cpu && (
|
||||
<div data-cy="k8sAppDetail-cpuReservation">
|
||||
CPU {applicationRequests.cpu}
|
||||
</div>
|
||||
)}
|
||||
{!!applicationRequests?.memoryBytes && (
|
||||
<div data-cy="k8sAppDetail-memoryReservation">
|
||||
Memory{' '}
|
||||
{bytesToReadableFormat(applicationRequests.memoryBytes)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Creation</td>
|
||||
<td>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{applicationOwner && (
|
||||
<span
|
||||
className="flex items-center gap-1"
|
||||
data-cy="k8sAppDetail-owner"
|
||||
>
|
||||
<User />
|
||||
{applicationOwner}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="flex items-center gap-1"
|
||||
data-cy="k8sAppDetail-creationDate"
|
||||
>
|
||||
<Clock />
|
||||
{moment(application?.metadata?.creationTimestamp).format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
)}
|
||||
</span>
|
||||
{(!externalApplication || systemNamespace) && (
|
||||
<span
|
||||
className="flex items-center gap-1"
|
||||
data-cy="k8sAppDetail-creationMethod"
|
||||
>
|
||||
<Clock />
|
||||
Deployed from {applicationDeployMethod}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<form className="form-horizontal">
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12 vertical-center">
|
||||
<Edit /> Note
|
||||
<Button
|
||||
size="xsmall"
|
||||
type="button"
|
||||
color="light"
|
||||
data-cy="k8sAppDetail-expandNoteButton"
|
||||
onClick={() => setIsNoteOpen(!isNoteOpen)}
|
||||
>
|
||||
{isNoteOpen ? 'Collapse' : 'Expand'}
|
||||
{isNoteOpen ? <ChevronUp /> : <ChevronDown />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isNoteOpen && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<textarea
|
||||
className="form-control resize-y"
|
||||
name="application_note"
|
||||
id="application_note"
|
||||
value={applicationNoteFormValues}
|
||||
onChange={(e) =>
|
||||
setApplicationNoteFormValues(e.target.value)
|
||||
}
|
||||
rows={5}
|
||||
placeholder="Enter a note about this application..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Authorized authorizations="K8sApplicationDetailsW">
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
color="primary"
|
||||
size="small"
|
||||
className="!ml-0"
|
||||
type="button"
|
||||
onClick={() => patchApplicationNote()}
|
||||
disabled={
|
||||
// disable if there is no change to the note, or it's updating
|
||||
applicationNoteFormValues ===
|
||||
(applicationNote || '') ||
|
||||
patchApplicationMutation.isLoading
|
||||
}
|
||||
data-cy="k8sAppDetail-saveNoteButton"
|
||||
isLoading={patchApplicationMutation.isLoading}
|
||||
loadingText={applicationNote ? 'Updating' : 'Saving'}
|
||||
>
|
||||
{applicationNote ? 'Update' : 'Save'} note
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</DetailsTable>
|
||||
</div>
|
||||
);
|
||||
|
||||
async function patchApplicationNote() {
|
||||
const path = `/metadata/annotations/${appNoteAnnotation}`;
|
||||
const value = applicationNoteFormValues;
|
||||
if (application?.kind) {
|
||||
try {
|
||||
await patchApplicationMutation.mutateAsync({
|
||||
appKind: application.kind,
|
||||
path,
|
||||
value,
|
||||
});
|
||||
notifySuccess('Success', 'Application successfully updated');
|
||||
} catch (error) {
|
||||
notifyError(
|
||||
`Failed to ${applicationNote ? 'update' : 'save'} note`,
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getApplicationDeployMethod(application?: Application) {
|
||||
if (!application?.metadata?.labels?.[appDeployMethodLabel])
|
||||
return 'application form';
|
||||
if (application?.metadata?.labels?.[appDeployMethodLabel] === 'content') {
|
||||
return 'manifest';
|
||||
}
|
||||
return application?.metadata?.labels?.[appDeployMethodLabel];
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue