1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 13:29:41 +02:00

feat(app): migrate app parent view to react [EE-5361] (#10086)

Co-authored-by: testa113 <testa113>
This commit is contained in:
Ali 2023-08-27 23:01:35 +02:00 committed by GitHub
parent 531f88b947
commit 841ca1ebd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1448 additions and 810 deletions

View file

@ -1,4 +1,4 @@
import { User, Clock, Edit, ChevronRight, ChevronUp } from 'lucide-react';
import { User, Clock, Info } from 'lucide-react';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { Pod } from 'kubernetes-types/core/v1';
@ -10,7 +10,11 @@ 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 { LoadingButton } from '@@/buttons';
import { WidgetBody, Widget } from '@@/Widget';
import { InlineLoader } from '@@/InlineLoader';
import { Icon } from '@@/Icon';
import { Note } from '@@/Note';
import { isSystemNamespace } from '../../namespaces/utils';
import {
@ -44,13 +48,12 @@ export function ApplicationSummaryWidget() {
endpointId: environmentId,
},
} = stateAndParams;
const applicationQuery = useApplication(
const { data: application, ...applicationQuery } = useApplication(
environmentId,
namespace,
name,
resourceType
);
const application = applicationQuery.data;
const systemNamespace = isSystemNamespace(namespace);
const externalApplication = application && isExternalApplication(application);
const applicationRequests = application && getResourceRequests(application);
@ -59,7 +62,6 @@ export function ApplicationSummaryWidget() {
const applicationNote =
application?.metadata?.annotations?.[appNoteAnnotation];
const [isNoteOpen, setIsNoteOpen] = useState(true);
const [applicationNoteFormValues, setApplicationNoteFormValues] =
useState('');
@ -67,6 +69,9 @@ export function ApplicationSummaryWidget() {
setApplicationNoteFormValues(applicationNote || '');
}, [applicationNote]);
const failedCreateCondition = application?.status?.conditions?.find(
(condition) => condition.reason === 'FailedCreate'
);
const patchApplicationMutation = usePatchApplicationMutation(
environmentId,
namespace,
@ -74,191 +79,197 @@ export function ApplicationSummaryWidget() {
);
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>
<div className="row">
<div className="col-sm-12">
<Widget>
<WidgetBody>
{applicationQuery.isLoading && (
<InlineLoader>Loading application...</InlineLoader>
)}
{!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">
<Button
size="small"
type="button"
color="none"
data-cy="k8sAppDetail-expandNoteButton"
onClick={() => setIsNoteOpen(!isNoteOpen)}
className="!m-0 !p-0"
{application && (
<>
{failedCreateCondition && (
<div
className="vertical-center alert alert-danger mb-2"
data-cy="k8sAppDetail-failedCreateMessage"
>
{isNoteOpen ? <ChevronUp /> : <ChevronRight />} <Edit />{' '}
Note
</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..."
/>
<Icon icon={Info} className="mr-1" mode="danger" />
<div>
<div className="font-semibold">
Failed to create application
</div>
{failedCreateCondition.message}
</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>
)}
<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>
</div>
</Authorized>
</>
)}
</form>
</td>
</tr>
</DetailsTable>
</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">
<Note
value={applicationNoteFormValues}
onChange={setApplicationNoteFormValues}
defaultIsOpen
isExpandable
/>
<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>
</>
)}
</WidgetBody>
</Widget>
</div>
</div>
);