Improvements 01 28 (#264)

* improving footer

* improve ui

* upgrading dependencies

* adding glow to all boxes on dashboard

* introducing single listing view

* next release version

* improve screenshots and login page
This commit is contained in:
Christian Kellner
2026-01-28 14:27:03 +01:00
committed by GitHub
parent 3117044139
commit 472169693f
29 changed files with 999 additions and 383 deletions

View File

@@ -1,92 +1,67 @@
@import "DashboardCardColors.less";
.color-variant(@bg, @border, @text) {
background-color: @bg;
border: 1px solid @border;
color: @text;
}
.dashboard-card {
box-sizing: border-box;
padding: .8rem;
border-radius: .5rem;
border-width: 1px;
font-weight: 600;
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
/* Make all KPI boxes the same size regardless of content/font */
width: 100%;
max-width: none;
height: 10rem;
display: flex;
flex-direction: column;
height: 140px;
margin-bottom: 16px;
transition: transform 0.2s, box-shadow 0.2s;
background-color: rgba(36, 36, 36, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--semi-color-border);
&.blue {
.color-variant(@color-blue-bg, @color-blue-border, @color-blue-text);
}
&:hover {
transform: translateY(-4px);
background-color: rgba(36, 36, 36, 1);
&.orange {
.color-variant(@color-orange-bg, @color-orange-border, @color-orange-text);
}
&.green {
.color-variant(@color-green-bg, @color-green-border, @color-green-text);
}
&.purple {
.color-variant(@color-purple-bg, @color-purple-border, @color-purple-text);
}
&.gray {
.color-variant(@color-gray-bg, @color-gray-border, @color-gray-text);
}
&__header {
display: flex;
align-items: center;
gap: .6rem;
/* Keep header from growing content height */
min-height: 2rem;
overflow: hidden;
&.blue {
box-shadow: 0 8px 24px -5px var(--semi-color-primary);
}
&.orange {
box-shadow: 0 8px 24px -5px var(--semi-color-warning);
}
&.green {
box-shadow: 0 8px 24px -5px var(--semi-color-success);
}
&.purple {
box-shadow: 0 8px 24px -5px var(--semi-color-info);
}
&.gray {
box-shadow: 0 8px 24px -5px rgba(255, 255, 255, 0.4);
}
}
&__icon {
border-radius: .6rem;
display: grid;
place-items: center;
}
&__title {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
}
&__content {
margin-top: .4rem;
font-size: .7rem;
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
width: 100%;
}
&__value {
margin: 0;
font-size: 1.5rem;
line-height: 1.1;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 700;
margin-bottom: 4px;
color: var(--semi-color-text-0);
}
&__desc {
opacity: .8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.blue {
box-shadow: 0 4px 20px -5px var(--semi-color-primary);
}
&.orange {
box-shadow: 0 4px 20px -5px var(--semi-color-warning);
}
&.green {
box-shadow: 0 4px 20px -5px var(--semi-color-success);
}
&.purple {
box-shadow: 0 4px 20px -5px var(--semi-color-info);
}
&.gray {
box-shadow: 0 4px 20px -5px rgba(255, 255, 255, 0.2);
}
}

View File

@@ -3,12 +3,8 @@
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { Card, Typography, Space } from '@douyinfe/semi-ui-19';
import './DashboardCard.less';
export default function KpiCard({
@@ -20,21 +16,28 @@ export default function KpiCard({
color = 'gray',
children,
}) {
const { Text } = Typography;
return (
<div className={`dashboard-card ${color}`}>
<div className="dashboard-card__header">
<div className="dashboard-card__icon">{icon}</div>
<div className="dashboard-card__title">
<span>{title}</span>
<Card className={`dashboard-card ${color}`} bodyStyle={{ padding: '16px' }}>
<Space vertical align="start" spacing="tight" style={{ width: '100%' }}>
<Space>
<div className="dashboard-card__icon">{icon}</div>
<Text strong className="dashboard-card__title">
{title}
</Text>
</Space>
<div className="dashboard-card__content">
<div className="dashboard-card__value" style={{ fontSize: valueFontSize }}>
{value}
{children}
</div>
{description && (
<Text size="small" type="tertiary" className="dashboard-card__desc">
{description}
</Text>
)}
</div>
</div>
<div className="dashboard-card__content">
<p className="dashboard-card__value" style={{ fontSize: valueFontSize }}>
{value}
{children}
</p>
{description && <span className="dashboard-card__desc">{description}</span>}
</div>
</div>
</Space>
</Card>
);
}

View File

@@ -6,19 +6,23 @@
import React from 'react';
import './FredyFooter.less';
import { useSelector } from '../../services/state/store.js';
import { Typography } from '@douyinfe/semi-ui-19';
import { Typography, Layout, Space, Divider } from '@douyinfe/semi-ui-19';
export default function FredyFooter() {
const { Text } = Typography;
const { Footer } = Layout;
const version = useSelector((state) => state.versionUpdate.versionUpdate);
return (
<div className="fredyFooter">
<div className="fredyFooter__version">
<Text type="tertiary">Fredy V{version?.localFredyVersion || 'N/A'}</Text>
</div>
<div className="fredyFooter__copyRight">
<Text link={{ href: 'https://github.com/orangecoding', target: '_blank' }}>Made with </Text>
</div>
</div>
<Footer className="fredyFooter">
<Space split={<Divider layout="vertical" />}>
<Text type="tertiary" size="small">
Fredy V{version?.localFredyVersion || 'N/A'}
</Text>
<Text size="small" link={{ href: 'https://github.com/orangecoding', target: '_blank' }}>
Made with
</Text>
</Space>
</Footer>
);
}

View File

@@ -1,20 +1,12 @@
.fredyFooter {
background:rgb(53, 54, 60);
color: white;
background-color: var(--semi-color-bg-1);
display: flex;
justify-content: space-between;
justify-content: flex-end;
align-items: center;
height: 1.7rem;
border-radius: .3rem;
border-top: 1px solid #45464b;
&__version {
padding-left: .5rem;
font-size: small;
}
&__copyRight {
padding-right: 1rem;
}
padding: 0 1rem;
height: 32px;
border-top: 1px solid var(--semi-color-border);
z-index: 1000;
position: relative;
flex-shrink: 0;
}

View File

@@ -185,31 +185,21 @@ const JobGrid = () => {
return (
<div className="jobGrid">
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Button
style={{ width: '7rem', margin: 0 }}
type="primary"
icon={<IconPlusCircle />}
className="jobs__newButton"
onClick={() => navigate('/jobs/new')}
>
<Space vertical align="start" style={{ width: '100%', marginBottom: '16px' }} spacing="medium">
<Button type="primary" icon={<IconPlusCircle />} onClick={() => navigate('/jobs/new')}>
New Job
</Button>
<div className="jobGrid__searchbar">
<div className="jobGrid__searchbar" style={{ width: '100%' }}>
<Input prefix={<IconSearch />} showClear placeholder="Search" onChange={handleFilterChange} />
<Popover content="Filter / Sort Results" style={{ color: 'white', padding: '.5rem' }}>
<div>
<Button
icon={<IconFilter />}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
</div>
</Popover>
<Button
icon={<IconFilter />}
style={{ marginLeft: '8px' }}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
</div>
</div>
</Space>
{showFilterBar && (
<div className="jobGrid__toolbar">
@@ -277,7 +267,6 @@ const JobGrid = () => {
<Card
className="jobGrid__card"
bodyStyle={{ padding: '16px' }}
headerLine={true}
title={
<div className="jobGrid__header">
<Title heading={5} ellipsis={{ showTooltip: true }} className="jobGrid__title">
@@ -351,6 +340,8 @@ const JobGrid = () => {
<div>
<Button
type="primary"
style={{ background: '#21aa21b5' }}
size="small"
theme="solid"
icon={<IconPlayCircle />}
disabled={job.isOnlyShared || job.running}
@@ -362,7 +353,7 @@ const JobGrid = () => {
<div>
<Button
type="secondary"
theme="solid"
size="small"
icon={<IconEdit />}
disabled={job.isOnlyShared}
onClick={() => navigate(`/jobs/edit/${job.id}`)}
@@ -373,7 +364,7 @@ const JobGrid = () => {
<div>
<Button
type="tertiary"
theme="solid"
size="small"
icon={<IconCopy />}
disabled={job.isOnlyShared}
onClick={() => navigate('/jobs/new', { state: { cloneFrom: job.id } })}
@@ -384,7 +375,7 @@ const JobGrid = () => {
<div>
<Button
type="danger"
theme="solid"
size="small"
icon={<IconDescend2 />}
disabled={job.isOnlyShared}
onClick={() => onListingRemoval(job.id)}
@@ -395,7 +386,7 @@ const JobGrid = () => {
<div>
<Button
type="danger"
theme="solid"
size="small"
icon={<IconDelete />}
disabled={job.isOnlyShared}
onClick={() => onJobRemoval(job.id)}

View File

@@ -1,11 +1,16 @@
.jobGrid {
&__card {
height: 100%;
transition: transform 0.2s;
transition: transform 0.2s, box-shadow 0.2s;
background-color: rgba(36, 36, 36, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--semi-color-border);
box-shadow: 0 0 15px -3px rgb(78 78 78 / 50%);
&:hover {
transform: translateY(-4px);
box-shadow: var(--semi-shadow-elevated);
box-shadow: 0 0 15px -3px rgb(78 78 78 / 70%);
background-color: rgba(36, 36, 36, 1);
}
}
@@ -19,12 +24,14 @@
&__toolbar {
&__card {
border-radius: 5px;
border-radius: var(--semi-border-radius-medium);
display: flex;
flex-direction: column;
gap: .3rem;
background: #232429;
background: rgba(36, 36, 36, 0.9);
backdrop-filter: blur(8px);
padding: 0.5rem;
border: 1px solid var(--semi-color-border);
}
}

View File

@@ -32,7 +32,9 @@ import {
IconSearch,
IconFilter,
IconActivity,
IconEyeOpened,
} from '@douyinfe/semi-icons';
import { useNavigate } from 'react-router-dom';
import no_image from '../../../assets/no_image.jpg';
import * as timeService from '../../../services/time/timeService.js';
import { xhrDelete, xhrPost } from '../../../services/xhr.js';
@@ -49,6 +51,7 @@ const ListingsGrid = () => {
const providers = useSelector((state) => state.provider);
const jobs = useSelector((state) => state.jobsData.jobs);
const actions = useActions();
const navigate = useNavigate();
const [page, setPage] = useState(1);
const pageSize = 40;
@@ -223,6 +226,8 @@ const ListingsGrid = () => {
<Col key={item.id} xs={24} sm={12} md={8} lg={6} xl={4} xxl={6}>
<Card
className={`listingsGrid__card ${!item.is_active ? 'listingsGrid__card--inactive' : ''}`}
style={{ cursor: 'pointer' }}
onClick={() => navigate(`/listings/listing/${item.id}`)}
cover={
<div style={{ position: 'relative' }}>
<div className="listingsGrid__imageContainer">
@@ -289,17 +294,26 @@ const ListingsGrid = () => {
</Space>
<Divider margin=".6rem" />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div className="listingsGrid__linkButton">
<div className="listingsGrid__linkButton" onClick={(e) => e.stopPropagation()}>
<a href={item.link} target="_blank" rel="noopener noreferrer">
<IconLink />
</a>
</div>
<Button
type="secondary"
size="small"
title="View Details"
onClick={() => navigate(`/listings/listing/${item.id}`)}
icon={<IconEyeOpened />}
/>
<Button
title="Remove"
type="danger"
size="small"
onClick={async () => {
onClick={async (e) => {
e.stopPropagation();
try {
await xhrDelete('/api/listings/', { ids: [item.id] });
Toast.success('Listing(s) successfully removed');

View File

@@ -33,11 +33,15 @@
&__card {
height: 100%;
transition: transform 0.2s;
transition: transform 0.2s, box-shadow 0.2s;
background-color: rgba(36, 36, 36, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--semi-color-border);
&:hover {
transform: translateY(-4px);
box-shadow: var(--semi-shadow-elevated);
background-color: rgba(36, 36, 36, 1);
}
&--inactive {
@@ -90,13 +94,15 @@
}
&__toolbar {
&__card {
border-radius: 5px;
border-radius: var(--semi-border-radius-medium);
display: flex;
flex-direction: column;
gap: .3rem;
background: #232429;
background: rgba(36, 36, 36, 0.9);
backdrop-filter: blur(8px);
padding: 0.5rem;
border: 1px solid var(--semi-color-border);
}
}
@@ -105,7 +111,7 @@
}
&__linkButton {
background: var(--semi-color-fill-0);
background: var(--semi-color-primary);
font-size: 14px;
line-height: 20px;
font-weight: 600;
@@ -115,5 +121,18 @@
align-items: center;
justify-content: center;
border-radius: 3px;
a {
color: white;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
&:hover {
background: var(--semi-color-primary-hover);
}
}
}

View File

@@ -6,5 +6,6 @@
gap: 0.5rem;
width: 100%;
display: flex;
padding-bottom: 12px;
}
}

View File

@@ -70,20 +70,18 @@ export default function Navigation({ isAdmin }) {
return (
<Nav
style={{ height: '100%' }}
style={{ height: '100%', maxWidth: collapsed ? '60px' : '240px' }}
items={items}
isCollapsed={collapsed}
selectedKeys={[parsePathName(location.pathname)]}
onSelect={(key) => {
navigate(key.itemKey);
}}
header={<img src={collapsed ? heart : logoWhite} width={collapsed ? '80' : '160'} alt="Fredy Logo" />}
header={<img src={collapsed ? heart : logoWhite} width={collapsed ? '30' : '120'} alt="Fredy Logo" />}
footer={
<Nav.Footer className="navigate__footer">
<Logout text={!collapsed} />
<Button icon={<IconSidebar />} onClick={() => setCollapsed(!collapsed)}>
{!collapsed && 'Collapse'}
</Button>
<Button icon={<IconSidebar />} onClick={() => setCollapsed(!collapsed)} />
</Nav.Footer>
}
/>

View File

@@ -7,8 +7,10 @@ import React from 'react';
import { Empty, Table, Button } from '@douyinfe/semi-ui-19';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
import { Typography } from '@douyinfe/semi-ui';
export default function ProviderTable({ providerData = [], onRemove, onEdit } = {}) {
const { Text } = Typography;
return (
<Table
pagination={false}
@@ -22,11 +24,7 @@ export default function ProviderTable({ providerData = [], onRemove, onEdit } =
title: 'URL',
dataIndex: 'url',
render: (_, data) => {
return (
<a href={data.url} target="_blank" rel="noopener noreferrer">
Visit site
</a>
);
return <Text link={{ href: data.url, target: '_blank' }}>Open Provider</Text>;
},
},
{