mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
703c602527 |
@@ -126,4 +126,21 @@ export default async function userSettingsPlugin(fastify) {
|
|||||||
return reply.code(500).send({ error: error.message });
|
return reply.code(500).send({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.post('/jobs-view-mode', async (request, reply) => {
|
||||||
|
const userId = request.session.currentUser;
|
||||||
|
const { jobs_view_mode } = request.body;
|
||||||
|
|
||||||
|
if (jobs_view_mode !== 'grid' && jobs_view_mode !== 'table') {
|
||||||
|
return reply.code(400).send({ error: 'jobs_view_mode must be "grid" or "table".' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
upsertSettings({ jobs_view_mode }, userId);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating jobs view mode setting', error);
|
||||||
|
return reply.code(500).send({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fredy",
|
"name": "fredy",
|
||||||
"version": "21.2.0",
|
"version": "21.3.0",
|
||||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
|
|||||||
BIN
ui/src/assets/news/2.png
Normal file
BIN
ui/src/assets/news/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 367 KiB |
@@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"key": "00e6b81777a275f5a140fc9101cb943810db6a69f6eb3927319c5aee0c876515",
|
"key": "00e6b81777a275f5a140fc9101cb943810db6a69f6eb3927319c5aee0c876221",
|
||||||
"content":
|
"content":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"title": "Table overview for listings",
|
"title": "Table overview for listings",
|
||||||
"text": "Thanks to https://github.com/datenwurm, we now have a table overview for listings. If you decide to use the table view, the decision will be stored.",
|
"text": "Thanks to https://github.com/datenwurm, we now have a table overview for listings. If you decide to use the table view, the decision will be stored.",
|
||||||
"media": "1.png"
|
"media": "1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Table overview for jobs",
|
||||||
|
"text": "Based on datenwurm's, work, I created a table overview for jobs. If you decide to use the table view, the decision will be stored.",
|
||||||
|
"media": "2.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
Empty,
|
Empty,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
|
Tooltip,
|
||||||
} from '@douyinfe/semi-ui-19';
|
} from '@douyinfe/semi-ui-19';
|
||||||
import {
|
import {
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
@@ -35,6 +36,8 @@ import {
|
|||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconHome,
|
IconHome,
|
||||||
|
IconGridView,
|
||||||
|
IconList,
|
||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import ListingDeletionModal from '../../ListingDeletionModal.jsx';
|
import ListingDeletionModal from '../../ListingDeletionModal.jsx';
|
||||||
@@ -42,6 +45,7 @@ import { useActions, useSelector } from '../../../services/state/store.js';
|
|||||||
import { xhrDelete, xhrPut, xhrPost } from '../../../services/xhr.js';
|
import { xhrDelete, xhrPut, xhrPost } from '../../../services/xhr.js';
|
||||||
import { debounce } from '../../../utils';
|
import { debounce } from '../../../utils';
|
||||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||||
|
import JobsTable from '../../table/JobsTable.jsx';
|
||||||
|
|
||||||
import './JobGrid.less';
|
import './JobGrid.less';
|
||||||
|
|
||||||
@@ -54,6 +58,9 @@ const JobGrid = () => {
|
|||||||
const actions = useActions();
|
const actions = useActions();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const userSettings = useSelector((state) => state.userSettings.settings);
|
||||||
|
const viewMode = userSettings?.jobs_view_mode ?? 'grid';
|
||||||
|
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const pageSize = 12;
|
const pageSize = 12;
|
||||||
|
|
||||||
@@ -234,6 +241,27 @@ const JobGrid = () => {
|
|||||||
onClick={() => setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))}
|
onClick={() => setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))}
|
||||||
title={sortDir === 'asc' ? 'Ascending' : 'Descending'}
|
title={sortDir === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="jobGrid__topbar__view-toggle">
|
||||||
|
<Tooltip content="Grid view">
|
||||||
|
<Button
|
||||||
|
icon={<IconGridView />}
|
||||||
|
theme={viewMode === 'grid' ? 'solid' : 'borderless'}
|
||||||
|
onClick={() => actions.userSettings.setJobsViewMode('grid')}
|
||||||
|
aria-label="Grid view"
|
||||||
|
aria-pressed={viewMode === 'grid'}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Table view">
|
||||||
|
<Button
|
||||||
|
icon={<IconList />}
|
||||||
|
theme={viewMode === 'table' ? 'solid' : 'borderless'}
|
||||||
|
onClick={() => actions.userSettings.setJobsViewMode('table')}
|
||||||
|
aria-label="Table view"
|
||||||
|
aria-pressed={viewMode === 'table'}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(jobsData?.result || []).length === 0 && (
|
{(jobsData?.result || []).length === 0 && (
|
||||||
@@ -244,136 +272,144 @@ const JobGrid = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Row gutter={[16, 16]}>
|
{viewMode === 'grid' ? (
|
||||||
{(jobsData?.result || []).map((job) => (
|
<Row gutter={[16, 16]}>
|
||||||
<Col key={job.id} xs={24} sm={12} md={12} lg={8} xl={8} xxl={6}>
|
{(jobsData?.result || []).map((job) => (
|
||||||
<Card className="jobGrid__card" bodyStyle={{ padding: '16px' }}>
|
<Col key={job.id} xs={24} sm={12} md={12} lg={8} xl={8} xxl={6}>
|
||||||
<div className="jobGrid__card__header">
|
<Card className="jobGrid__card" bodyStyle={{ padding: '16px' }}>
|
||||||
<div className="jobGrid__card__name">
|
<div className="jobGrid__card__header">
|
||||||
<span className={`jobGrid__card__dot${job.enabled ? ' jobGrid__card__dot--active' : ''}`} />
|
<div className="jobGrid__card__name">
|
||||||
<Title heading={5} ellipsis={{ showTooltip: true }} className="jobGrid__title">
|
<span className={`jobGrid__card__dot${job.enabled ? ' jobGrid__card__dot--active' : ''}`} />
|
||||||
{job.name}
|
<Title heading={5} ellipsis={{ showTooltip: true }} className="jobGrid__title">
|
||||||
</Title>
|
{job.name}
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
|
||||||
|
{job.isOnlyShared && (
|
||||||
|
<Popover content={getPopoverContent('This job has been shared with you — read only.')}>
|
||||||
|
<div>
|
||||||
|
<IconAlertTriangle style={{ color: 'rgba(var(--semi-yellow-7), 1)' }} />
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{job.running && (
|
||||||
|
<Tag color="green" variant="light" size="small">
|
||||||
|
RUNNING
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
|
|
||||||
{job.isOnlyShared && (
|
<div className="jobGrid__card__stats">
|
||||||
<Popover
|
<div className="jobGrid__card__stat jobGrid__card__stat--blue">
|
||||||
content={getPopoverContent(
|
<span className="jobGrid__card__stat__number">{job.numberOfFoundListings || 0}</span>
|
||||||
'This job has been shared with you by another user, therefor it is read-only.',
|
<span className="jobGrid__card__stat__label">
|
||||||
)}
|
<IconHome size="small" /> Listings
|
||||||
>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="jobGrid__card__stat jobGrid__card__stat--orange">
|
||||||
|
<span className="jobGrid__card__stat__number">{job.provider?.length || 0}</span>
|
||||||
|
<span className="jobGrid__card__stat__label">
|
||||||
|
<IconBriefcase size="small" /> Providers
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="jobGrid__card__stat jobGrid__card__stat--purple">
|
||||||
|
<span className="jobGrid__card__stat__number">{job.notificationAdapter?.length || 0}</span>
|
||||||
|
<span className="jobGrid__card__stat__label">
|
||||||
|
<IconBell size="small" /> Adapters
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider margin="12px" />
|
||||||
|
|
||||||
|
<div className="jobGrid__card__footer">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<Switch
|
||||||
|
onChange={(checked) => onJobStatusChanged(job.id, checked)}
|
||||||
|
checked={job.enabled}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Text type="secondary" size="small">
|
||||||
|
Active
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className="jobGrid__actions">
|
||||||
|
<Popover content={getPopoverContent('Run Job')}>
|
||||||
<div>
|
<div>
|
||||||
<IconAlertTriangle style={{ color: 'rgba(var(--semi-yellow-7), 1)' }} />
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ background: '#21aa21b5' }}
|
||||||
|
size="small"
|
||||||
|
theme="solid"
|
||||||
|
icon={<IconPlayCircle />}
|
||||||
|
disabled={job.isOnlyShared || job.running}
|
||||||
|
onClick={() => onJobRun(job.id)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
<Popover content={getPopoverContent('Edit a Job')}>
|
||||||
{job.running && (
|
<div>
|
||||||
<Tag color="green" variant="light" size="small">
|
<Button
|
||||||
RUNNING
|
type="secondary"
|
||||||
</Tag>
|
size="small"
|
||||||
)}
|
icon={<IconEdit />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => navigate(`/jobs/edit/${job.id}`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
<Popover content={getPopoverContent('Clone Job')}>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
icon={<IconCopy />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => navigate('/jobs/new', { state: { cloneFrom: job.id } })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
<Popover content={getPopoverContent('Delete all found Listings of this Job')}>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
icon={<IconDescend2 />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onListingRemoval(job.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
<Popover content={getPopoverContent('Delete Job')}>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
icon={<IconDelete />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onJobRemoval(job.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
|
</Col>
|
||||||
<div className="jobGrid__card__stats">
|
))}
|
||||||
<div className="jobGrid__card__stat jobGrid__card__stat--blue">
|
</Row>
|
||||||
<span className="jobGrid__card__stat__number">{job.numberOfFoundListings || 0}</span>
|
) : (
|
||||||
<span className="jobGrid__card__stat__label">
|
<JobsTable
|
||||||
<IconHome size="small" /> Listings
|
jobs={jobsData?.result || []}
|
||||||
</span>
|
onRun={onJobRun}
|
||||||
</div>
|
onEdit={(id) => navigate(`/jobs/edit/${id}`)}
|
||||||
<div className="jobGrid__card__stat jobGrid__card__stat--orange">
|
onClone={(id) => navigate('/jobs/new', { state: { cloneFrom: id } })}
|
||||||
<span className="jobGrid__card__stat__number">{job.provider.length || 0}</span>
|
onDeleteListings={onListingRemoval}
|
||||||
<span className="jobGrid__card__stat__label">
|
onDeleteJob={onJobRemoval}
|
||||||
<IconBriefcase size="small" /> Providers
|
onStatusChange={onJobStatusChanged}
|
||||||
</span>
|
/>
|
||||||
</div>
|
)}
|
||||||
<div className="jobGrid__card__stat jobGrid__card__stat--purple">
|
|
||||||
<span className="jobGrid__card__stat__number">{job.notificationAdapter.length || 0}</span>
|
|
||||||
<span className="jobGrid__card__stat__label">
|
|
||||||
<IconBell size="small" /> Adapters
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider margin="12px" />
|
|
||||||
|
|
||||||
<div className="jobGrid__card__footer">
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
||||||
<Switch
|
|
||||||
onChange={(checked) => onJobStatusChanged(job.id, checked)}
|
|
||||||
checked={job.enabled}
|
|
||||||
disabled={job.isOnlyShared}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<Text type="secondary" size="small">
|
|
||||||
Active
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className="jobGrid__actions">
|
|
||||||
<Popover content={getPopoverContent('Run Job')}>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
style={{ background: '#21aa21b5' }}
|
|
||||||
size="small"
|
|
||||||
theme="solid"
|
|
||||||
icon={<IconPlayCircle />}
|
|
||||||
disabled={job.isOnlyShared || job.running}
|
|
||||||
onClick={() => onJobRun(job.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
<Popover content={getPopoverContent('Edit a Job')}>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="secondary"
|
|
||||||
size="small"
|
|
||||||
icon={<IconEdit />}
|
|
||||||
disabled={job.isOnlyShared}
|
|
||||||
onClick={() => navigate(`/jobs/edit/${job.id}`)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
<Popover content={getPopoverContent('Clone Job')}>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
size="small"
|
|
||||||
icon={<IconCopy />}
|
|
||||||
disabled={job.isOnlyShared}
|
|
||||||
onClick={() => navigate('/jobs/new', { state: { cloneFrom: job.id } })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
<Popover content={getPopoverContent('Delete all found Listings of this Job')}>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
icon={<IconDescend2 />}
|
|
||||||
disabled={job.isOnlyShared}
|
|
||||||
onClick={() => onListingRemoval(job.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
<Popover content={getPopoverContent('Delete Job')}>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
icon={<IconDelete />}
|
|
||||||
disabled={job.isOnlyShared}
|
|
||||||
onClick={() => onJobRemoval(job.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
{(jobsData?.result || []).length > 0 && jobsData?.totalNumber > 12 && (
|
{(jobsData?.result || []).length > 0 && jobsData?.totalNumber > 12 && (
|
||||||
<div className="jobGrid__pagination">
|
<div className="jobGrid__pagination">
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
@@ -17,6 +17,12 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__view-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
|
|||||||
128
ui/src/components/table/JobsTable.jsx
Normal file
128
ui/src/components/table/JobsTable.jsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 by Christian Kellner.
|
||||||
|
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Button, Tag, Tooltip, Switch } from '@douyinfe/semi-ui-19';
|
||||||
|
import {
|
||||||
|
IconAlertTriangle,
|
||||||
|
IconBell,
|
||||||
|
IconBriefcase,
|
||||||
|
IconCopy,
|
||||||
|
IconDelete,
|
||||||
|
IconDescend2,
|
||||||
|
IconEdit,
|
||||||
|
IconHome,
|
||||||
|
IconPlayCircle,
|
||||||
|
} from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import './JobsTable.less';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ jobs: object[], onRun: Function, onEdit: Function, onClone: Function, onDeleteListings: Function, onDeleteJob: Function, onStatusChange: Function }} props
|
||||||
|
*/
|
||||||
|
const JobsTable = ({ jobs, onRun, onEdit, onClone, onDeleteListings, onDeleteJob, onStatusChange }) => (
|
||||||
|
<div className="jobsTable">
|
||||||
|
{jobs.map((job) => (
|
||||||
|
<div key={job.id} className={`jobsTable__row${!job.enabled ? ' jobsTable__row--inactive' : ''}`}>
|
||||||
|
<div className="jobsTable__row__dot">
|
||||||
|
<span
|
||||||
|
className={`jobsTable__row__dot__indicator${job.enabled ? ' jobsTable__row__dot__indicator--active' : ''}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__name" title={job.name}>
|
||||||
|
{job.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__stat jobsTable__row__stat--blue">
|
||||||
|
<IconHome size="small" />
|
||||||
|
{job.numberOfFoundListings || 0}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__stat jobsTable__row__stat--orange">
|
||||||
|
<IconBriefcase size="small" />
|
||||||
|
{job.provider?.length || 0}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__stat jobsTable__row__stat--purple">
|
||||||
|
<IconBell size="small" />
|
||||||
|
{job.notificationAdapter?.length || 0}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__badges">
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={job.enabled}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onChange={(checked) => onStatusChange(job.id, checked)}
|
||||||
|
/>
|
||||||
|
{job.running && (
|
||||||
|
<Tag color="green" variant="light" size="small">
|
||||||
|
RUNNING
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{job.isOnlyShared && (
|
||||||
|
<Tooltip content="Shared with you — read only">
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<IconAlertTriangle style={{ color: 'rgba(var(--semi-yellow-7), 1)' }} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="jobsTable__row__actions">
|
||||||
|
<Tooltip content="Run Job">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ background: '#21aa21b5' }}
|
||||||
|
size="small"
|
||||||
|
theme="solid"
|
||||||
|
icon={<IconPlayCircle />}
|
||||||
|
disabled={job.isOnlyShared || job.running}
|
||||||
|
onClick={() => onRun(job.id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Edit Job">
|
||||||
|
<Button
|
||||||
|
type="secondary"
|
||||||
|
size="small"
|
||||||
|
icon={<IconEdit />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onEdit(job.id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Clone Job">
|
||||||
|
<Button
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
icon={<IconCopy />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onClone(job.id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Delete all found Listings">
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
icon={<IconDescend2 />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onDeleteListings(job.id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Delete Job">
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
icon={<IconDelete />}
|
||||||
|
disabled={job.isOnlyShared}
|
||||||
|
onClick={() => onDeleteJob(job.id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default JobsTable;
|
||||||
105
ui/src/components/table/JobsTable.less
Normal file
105
ui/src/components/table/JobsTable.less
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
@import '../../tokens.less';
|
||||||
|
|
||||||
|
.jobsTable {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px 1fr 80px 80px 80px auto auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: @space-3;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: @color-elevated;
|
||||||
|
border: 1px solid @color-border;
|
||||||
|
border-radius: @radius-chip;
|
||||||
|
transition: background @transition-fast;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--inactive {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dot {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&__indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: rgba(251, 113, 133, 0.7);
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background-color: rgba(52, 211, 153, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: @text-sm;
|
||||||
|
color: @color-text;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__stat {
|
||||||
|
font-size: @text-sm;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--blue {
|
||||||
|
color: @color-blue-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--orange {
|
||||||
|
color: @color-orange-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--purple {
|
||||||
|
color: @color-purple-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__badges {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
grid-template-columns: 24px 1fr 80px auto auto;
|
||||||
|
|
||||||
|
.jobsTable__row__stat--orange,
|
||||||
|
.jobsTable__row__stat--purple {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
grid-template-columns: 24px 1fr auto auto;
|
||||||
|
|
||||||
|
.jobsTable__row__stat--blue {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -335,6 +335,20 @@ export const useFredyState = create(
|
|||||||
throw Exception;
|
throw Exception;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async setJobsViewMode(jobs_view_mode) {
|
||||||
|
try {
|
||||||
|
await xhrPost('/api/user/settings/jobs-view-mode', { jobs_view_mode });
|
||||||
|
set((state) => ({
|
||||||
|
userSettings: {
|
||||||
|
...state.userSettings,
|
||||||
|
settings: { ...state.userSettings.settings, jobs_view_mode },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} catch (Exception) {
|
||||||
|
console.error('Error while trying to update jobs view mode setting. Error:', Exception);
|
||||||
|
throw Exception;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user