Skip to content

Commit f33d0b1

Browse files
committed
Update schedule view UI
1 parent 33a43de commit f33d0b1

File tree

12 files changed

+230
-134
lines changed

12 files changed

+230
-134
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"name": "pterodactyl-panel",
33
"dependencies": {
4-
"@fortawesome/fontawesome-svg-core": "1.2.19",
5-
"@fortawesome/free-solid-svg-icons": "^5.9.0",
6-
"@fortawesome/react-fontawesome": "0.1.4",
4+
"@fortawesome/fontawesome-svg-core": "^1.2.32",
5+
"@fortawesome/free-solid-svg-icons": "^5.15.1",
6+
"@fortawesome/react-fontawesome": "^0.1.11",
77
"axios": "^0.19.2",
88
"chart.js": "^2.8.0",
99
"codemirror": "^5.57.0",
@@ -23,9 +23,9 @@
2323
"react": "^16.13.1",
2424
"react-dom": "npm:@hot-loader/react-dom",
2525
"react-fast-compare": "^3.2.0",
26+
"react-ga": "^3.1.2",
2627
"react-google-recaptcha": "^2.0.1",
2728
"react-helmet": "^6.1.0",
28-
"react-ga": "^3.1.2",
2929
"react-hot-loader": "^4.12.21",
3030
"react-i18next": "^11.2.1",
3131
"react-redux": "^7.1.0",

resources/scripts/components/elements/Button.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ const ButtonStyle = styled.button<Omit<Props, 'isLoading'>>`
5757
`};
5858
`};
5959
60-
${props => props.size === 'xsmall' && tw`p-2 text-xs`};
61-
${props => (!props.size || props.size === 'small') && tw`p-3`};
60+
${props => props.size === 'xsmall' && tw`px-2 py-1 text-xs`};
61+
${props => (!props.size || props.size === 'small') && tw`px-4 py-2`};
6262
${props => props.size === 'large' && tw`p-4 text-sm`};
6363
${props => props.size === 'xlarge' && tw`p-4 w-full`};
6464
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { CSSProperties } from 'react';
2+
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
3+
import tw from 'twin.macro';
4+
5+
interface Props {
6+
icon: IconDefinition;
7+
className?: string;
8+
style?: CSSProperties;
9+
}
10+
11+
const Icon = ({ icon, className, style }: Props) => {
12+
let [ width, height, , , paths ] = icon.icon;
13+
14+
paths = Array.isArray(paths) ? paths : [ paths ];
15+
16+
return (
17+
<svg
18+
xmlns={'http://www.w3.org/2000/svg'}
19+
viewBox={`0 0 ${width} ${height}`}
20+
css={tw`fill-current inline-block`}
21+
className={className}
22+
style={style}
23+
>
24+
{paths.map((path, index) => (
25+
<path key={`svg_path_${index}`} d={path}/>
26+
))}
27+
</svg>
28+
);
29+
};
30+
31+
export default Icon;

resources/scripts/components/elements/Modal.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
1+
import React, { useEffect, useMemo, useRef, useState } from 'react';
22
import Spinner from '@/components/elements/Spinner';
33
import tw from 'twin.macro';
44
import styled, { css } from 'styled-components/macro';
55
import { breakpoint } from '@/theme';
66
import Fade from '@/components/elements/Fade';
7+
import { createPortal } from 'react-dom';
78

89
export interface RequiredModalProps {
910
visible: boolean;
@@ -124,4 +125,10 @@ const Modal: React.FC<ModalProps> = ({ visible, appear, dismissable, showSpinner
124125
);
125126
};
126127

127-
export default Modal;
128+
const PortaledModal: React.FC<ModalProps> = ({ children, ...props }) => {
129+
const element = useRef(document.getElementById('modal-portal'));
130+
131+
return createPortal(<Modal {...props}>{children}</Modal>, element.current!);
132+
};
133+
134+
export default PortaledModal;

resources/scripts/components/server/schedules/DeleteScheduleButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default ({ scheduleId, onDeleted }: Props) => {
4949
Are you sure you want to delete this schedule? All tasks will be removed and any running processes
5050
will be terminated.
5151
</ConfirmationModal>
52-
<Button css={tw`mr-4`} color={'red'} isSecondary onClick={() => setVisible(true)}>
52+
<Button css={tw`flex-1 sm:flex-none mr-4 border-transparent`} color={'red'} isSecondary onClick={() => setVisible(true)}>
5353
Delete
5454
</Button>
5555
</>

resources/scripts/components/server/schedules/NewTaskButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
22
import { Schedule } from '@/api/server/schedules/getServerSchedules';
33
import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal';
44
import Button from '@/components/elements/Button';
5+
import tw from 'twin.macro';
56

67
interface Props {
78
schedule: Schedule;
@@ -18,7 +19,7 @@ export default ({ schedule }: Props) => {
1819
onDismissed={() => setVisible(false)}
1920
/>
2021
}
21-
<Button onClick={() => setVisible(true)}>
22+
<Button onClick={() => setVisible(true)} css={tw`flex-1`}>
2223
New Task
2324
</Button>
2425
</>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import tw from 'twin.macro';
3+
import { Schedule } from '@/api/server/schedules/getServerSchedules';
4+
5+
interface Props {
6+
cron: Schedule['cron'];
7+
className?: string;
8+
}
9+
10+
const ScheduleCronRow = ({ cron, className }: Props) => (
11+
<div css={tw`flex`} className={className}>
12+
<div css={tw`w-1/5 sm:w-auto text-center`}>
13+
<p css={tw`font-medium`}>{cron.minute}</p>
14+
<p css={tw`text-2xs text-neutral-500 uppercase`}>Minute</p>
15+
</div>
16+
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
17+
<p css={tw`font-medium`}>{cron.hour}</p>
18+
<p css={tw`text-2xs text-neutral-500 uppercase`}>Hour</p>
19+
</div>
20+
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
21+
<p css={tw`font-medium`}>{cron.dayOfMonth}</p>
22+
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Month)</p>
23+
</div>
24+
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
25+
<p css={tw`font-medium`}>*</p>
26+
<p css={tw`text-2xs text-neutral-500 uppercase`}>Month</p>
27+
</div>
28+
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
29+
<p css={tw`font-medium`}>{cron.dayOfWeek}</p>
30+
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Week)</p>
31+
</div>
32+
</div>
33+
);
34+
35+
export default ScheduleCronRow;

resources/scripts/components/server/schedules/ScheduleEditContainer.tsx

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useCallback, useEffect, useState } from 'react';
22
import { RouteComponentProps } from 'react-router-dom';
33
import { Schedule } from '@/api/server/schedules/getServerSchedules';
44
import getServerSchedule from '@/api/server/schedules/getServerSchedule';
55
import Spinner from '@/components/elements/Spinner';
66
import FlashMessageRender from '@/components/FlashMessageRender';
77
import { httpErrorToHuman } from '@/api/http';
8-
import ScheduleRow from '@/components/server/schedules/ScheduleRow';
9-
import ScheduleTaskRow from '@/components/server/schedules/ScheduleTaskRow';
108
import EditScheduleModal from '@/components/server/schedules/EditScheduleModal';
119
import NewTaskButton from '@/components/server/schedules/NewTaskButton';
1210
import DeleteScheduleButton from '@/components/server/schedules/DeleteScheduleButton';
@@ -16,7 +14,10 @@ import { ServerContext } from '@/state/server';
1614
import PageContentBlock from '@/components/elements/PageContentBlock';
1715
import tw from 'twin.macro';
1816
import Button from '@/components/elements/Button';
19-
import GreyRowBox from '@/components/elements/GreyRowBox';
17+
import ScheduleTaskRow from '@/components/server/schedules/ScheduleTaskRow';
18+
import isEqual from 'react-fast-compare';
19+
import { format } from 'date-fns';
20+
import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow';
2021

2122
interface Params {
2223
id: string;
@@ -26,6 +27,24 @@ interface State {
2627
schedule?: Schedule;
2728
}
2829

30+
const CronBox = ({ title, value }: { title: string; value: string }) => (
31+
<div css={tw`bg-neutral-700 rounded p-4`}>
32+
<p css={tw`text-neutral-300 text-sm`}>{title}</p>
33+
<p css={tw`text-2xl font-medium text-neutral-100`}>{value}</p>
34+
</div>
35+
);
36+
37+
const ActivePill = ({ active }: { active: boolean }) => (
38+
<span
39+
css={[
40+
tw`rounded-full px-2 py-px text-xs ml-4 uppercase`,
41+
active ? tw`bg-green-600 text-green-100` : tw`bg-red-600 text-red-100`,
42+
]}
43+
>
44+
{active ? 'Active' : 'Inactive'}
45+
</span>
46+
);
47+
2948
export default ({ match, history, location: { state } }: RouteComponentProps<Params, Record<string, unknown>, State>) => {
3049
const id = ServerContext.useStoreState(state => state.server.data!.id);
3150
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
@@ -34,7 +53,8 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
3453
const [ isLoading, setIsLoading ] = useState(true);
3554
const [ showEditModal, setShowEditModal ] = useState(false);
3655

37-
const schedule = ServerContext.useStoreState(st => st.schedules.data.find(s => s.id === state.schedule?.id), [ match ]);
56+
// @ts-ignore
57+
const schedule: Schedule | undefined = ServerContext.useStoreState(st => st.schedules.data.find(s => s.id === state.schedule?.id), isEqual);
3858
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
3959

4060
useEffect(() => {
@@ -53,59 +73,79 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
5373
.then(() => setIsLoading(false));
5474
}, [ match ]);
5575

76+
const toggleEditModal = useCallback(() => {
77+
setShowEditModal(s => !s);
78+
}, []);
79+
5680
return (
5781
<PageContentBlock>
5882
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
5983
{!schedule || isLoading ?
6084
<Spinner size={'large'} centered/>
6185
:
6286
<>
63-
<GreyRowBox css={tw`cursor-pointer mb-2 flex-wrap`}>
64-
<ScheduleRow schedule={schedule}/>
65-
</GreyRowBox>
66-
<EditScheduleModal
67-
visible={showEditModal}
68-
schedule={schedule}
69-
onDismissed={() => setShowEditModal(false)}
70-
/>
71-
<div css={tw`flex items-center mt-8 mb-4`}>
72-
<div css={tw`flex-1`}>
73-
<h2 css={tw`text-2xl`}>Configured Tasks</h2>
74-
</div>
87+
<ScheduleCronRow cron={schedule.cron} css={tw`sm:hidden bg-neutral-700 rounded mb-4 p-3`}/>
88+
<div css={tw`hidden sm:grid grid-cols-5 md:grid-cols-7 gap-4 mb-6`}>
89+
<CronBox title={'Minute'} value={schedule.cron.minute}/>
90+
<CronBox title={'Hour'} value={schedule.cron.hour}/>
91+
<CronBox title={'Day (Month)'} value={schedule.cron.dayOfMonth}/>
92+
<CronBox title={'Month'} value={'*'}/>
93+
<CronBox title={'Day (Week)'} value={schedule.cron.dayOfWeek}/>
7594
</div>
76-
{schedule.tasks.length > 0 ?
77-
<>
78-
{
79-
schedule.tasks
80-
.sort((a, b) => a.sequenceId - b.sequenceId)
81-
.map(task => (
82-
<ScheduleTaskRow key={task.id} task={task} schedule={schedule}/>
83-
))
84-
}
85-
{schedule.tasks.length > 1 &&
86-
<p css={tw`text-xs text-neutral-400`}>
87-
Task delays are relative to the previous task in the listing.
88-
</p>
95+
<div css={tw`rounded shadow`}>
96+
<div css={tw`sm:flex items-center bg-neutral-900 p-3 sm:p-6 border-b-4 border-neutral-600 rounded-t`}>
97+
<div css={tw`flex-1`}>
98+
<h3 css={tw`flex items-center text-neutral-100 text-2xl`}>
99+
{schedule.name}
100+
{schedule.isProcessing ?
101+
<span
102+
css={tw`flex items-center rounded-full px-2 py-px text-xs ml-4 uppercase bg-neutral-600 text-white`}
103+
>
104+
<Spinner css={tw`w-3! h-3! mr-2`}/>
105+
Processing
106+
</span>
107+
:
108+
<ActivePill active={schedule.isActive}/>
109+
}
110+
</h3>
111+
<p css={tw`mt-1 text-sm text-neutral-300`}>
112+
Last run at:&nbsp;
113+
{schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM do \'at\' h:mma') : 'never'}
114+
</p>
115+
</div>
116+
<div css={tw`flex sm:block mt-3 sm:mt-0`}>
117+
<Can action={'schedule.update'}>
118+
<Button
119+
isSecondary
120+
color={'grey'}
121+
size={'small'}
122+
css={tw`flex-1 mr-4 border-transparent`}
123+
onClick={toggleEditModal}
124+
>
125+
Edit
126+
</Button>
127+
<NewTaskButton schedule={schedule}/>
128+
</Can>
129+
</div>
130+
</div>
131+
<div css={tw`bg-neutral-700 rounded-b`}>
132+
{schedule.tasks.length > 0 ?
133+
schedule.tasks.map(task => (
134+
<ScheduleTaskRow key={`${schedule.id}_${task.id}`} task={task} schedule={schedule}/>
135+
))
136+
:
137+
null
89138
}
90-
</>
91-
:
92-
<p css={tw`text-sm text-neutral-400`}>
93-
There are no tasks configured for this schedule.
94-
</p>
95-
}
96-
<div css={tw`mt-8 flex justify-end`}>
139+
</div>
140+
</div>
141+
<EditScheduleModal visible={showEditModal} schedule={schedule} onDismissed={toggleEditModal}/>
142+
<div css={tw`mt-6 flex sm:justify-end`}>
97143
<Can action={'schedule.delete'}>
98144
<DeleteScheduleButton
99145
scheduleId={schedule.id}
100146
onDeleted={() => history.push(`/server/${id}/schedules`)}
101147
/>
102148
</Can>
103-
<Can action={'schedule.update'}>
104-
<Button css={tw`mr-4`} onClick={() => setShowEditModal(true)}>
105-
Edit
106-
</Button>
107-
<NewTaskButton schedule={schedule}/>
108-
</Can>
109149
</div>
110150
</>
111151
}

resources/scripts/components/server/schedules/ScheduleRow.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons';
55
import { format } from 'date-fns';
66
import tw from 'twin.macro';
7+
import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow';
78

89
export default ({ schedule }: { schedule: Schedule }) => (
910
<>
@@ -27,36 +28,19 @@ export default ({ schedule }: { schedule: Schedule }) => (
2728
{schedule.isActive ? 'Active' : 'Inactive'}
2829
</p>
2930
</div>
30-
<div css={tw`flex items-center mx-auto sm:mx-8 w-full sm:w-auto mt-4 sm:mt-0`}>
31-
<div css={tw`w-1/5 sm:w-auto text-center`}>
32-
<p css={tw`font-medium`}>{schedule.cron.minute}</p>
33-
<p css={tw`text-2xs text-neutral-500 uppercase`}>Minute</p>
34-
</div>
35-
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
36-
<p css={tw`font-medium`}>{schedule.cron.hour}</p>
37-
<p css={tw`text-2xs text-neutral-500 uppercase`}>Hour</p>
38-
</div>
39-
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
40-
<p css={tw`font-medium`}>{schedule.cron.dayOfMonth}</p>
41-
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Month)</p>
42-
</div>
43-
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
44-
<p css={tw`font-medium`}>*</p>
45-
<p css={tw`text-2xs text-neutral-500 uppercase`}>Month</p>
46-
</div>
47-
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
48-
<p css={tw`font-medium`}>{schedule.cron.dayOfWeek}</p>
49-
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Week)</p>
50-
</div>
51-
</div>
31+
<ScheduleCronRow cron={schedule.cron} css={tw`mx-auto sm:mx-8 w-full sm:w-auto mt-4 sm:mt-0`}/>
5232
<div>
5333
<p
5434
css={[
5535
tw`py-1 px-3 rounded text-xs uppercase text-white hidden sm:block`,
56-
schedule.isActive ? tw`bg-green-600` : tw`bg-neutral-400`,
36+
schedule.isActive && !schedule.isProcessing ? tw`bg-green-600` : tw`bg-neutral-400`,
5737
]}
5838
>
59-
{schedule.isActive ? 'Active' : 'Inactive'}
39+
{schedule.isProcessing ?
40+
'Processing'
41+
:
42+
schedule.isActive ? 'Active' : 'Inactive'
43+
}
6044
</p>
6145
</div>
6246
</>

0 commit comments

Comments
 (0)