completed modules

This commit is contained in:
Km.Van
2025-08-10 19:57:03 +08:00
parent 29089a4de4
commit c12f7bea7d
73 changed files with 608 additions and 607 deletions

View File

@@ -1,4 +1,3 @@
import { Cards } from '@/Components/Card/components/index.tsx';
import '@/Components/ColorScheme/components/config.scss';
import { type FC, useEffect, useState } from 'react';
import { ConfigStore } from '@/Components/Config/store.ts';
@@ -18,8 +17,10 @@ import { ServerStatusStore } from '@/Components/ServerStatus/components/store.ts
import { Toast } from '@/Components/Toast/components/index.tsx';
import { UserConfigStore } from '@/Components/UserConfig/store.ts';
import './global.scss';
import { Modules } from '@/Components/Module/components/index.tsx';
import { Nav } from '@/Components/Nav/components/index.tsx';
import { PollStore } from '@/Components/Poll/components/store.ts';
import { TemperatureSensorStore } from '@/Components/TemperatureSensor/components/store.ts';
import { BootstrapLoading } from './loading.tsx';
export const Bootstrap: FC = () => {
const [loading, setLoading] = useState(true);
@@ -42,6 +43,7 @@ export const Bootstrap: FC = () => {
ServerStatusStore.setPollData(data?.serverStatus);
ServerInfoStore.setPollData(data?.serverInfo);
NodesStore.setPollData(data?.nodes);
TemperatureSensorStore.setPollData(data?.temperatureSensor);
} else {
alert('Can not fetch data.');
}
@@ -66,7 +68,7 @@ export const Bootstrap: FC = () => {
return (
<>
<Header />
<Cards />
<Modules />
<Footer />
<Nav />
{/* <Forkme /> */}

View File

@@ -1,7 +0,0 @@
import type { FC, HTMLAttributes } from 'react';
import styles from './error.module.scss';
export const CardError: FC<HTMLAttributes<HTMLDivElement>> = ({ children }) => (
<div className={styles.main} role="alert">
{children}
</div>
);

View File

@@ -1,18 +0,0 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { PollStore } from '@/Components/Poll/components/store.ts';
import styles from './index.module.scss';
import { CardStore } from './store.ts';
export const Cards: FC = observer(() => {
const { cardsLength, enabledCards } = CardStore;
if (!cardsLength) {
return null;
}
return (
<div className={styles.container}>
{enabledCards.map(({ id, component: Component }) => {
return <Component key={id} />;
})}
</div>
);
});

View File

@@ -1,51 +0,0 @@
@use "../../Style/components/device.scss" as m;
:root {
--x-card-bg: hsl(0 0% 0% / 0.95);
--x-card-header-bg: hsl(0 0% 100% / 0.75);
--x-card-header-fg: hsl(0 0% 0%);
--x-card-header-title-fg: hsl(0 0% 0% / 0.7);
--x-card-header-title-bg: hsl(0 0% 0% / 0.1);
--x-card-body-bg: var(--x-card-header-bg);
--x-card-box-shadow: hsla(0 0% 20% 0.3) 0px -1px 0px hsl(0 0% 100%) 0px 1px 0px inset,
hsla(0 0% 20% 0.3) 0px -1px 0px inset hsl(0 0% 100%) 0px 1px 0px;
@media (prefers-color-scheme: dark) {
--x-card-bg: hsl(0 0% 15% / 0.95);
--x-card-header-bg: hsl(0 0% 100% / 0.1);
--x-card-header-fg: hsl(0 0% 100% / 0.7);
--x-card-header-title-fg: hsl(0 0% 100% / 0.7);
--x-card-header-title-bg: hsl(0 0% 100% / 0.1);
--x-card-body-bg: var(--x-card-header-bg);
--x-card-box-shadow: 0px 0px 0px 1px hsl(0 0% 0%) inset;
}
}
.main {
position: relative;
flex-grow: 1;
scroll-margin-top: 0;
}
.header {
display: flex;
align-items: center;
// z-index: 10;
border-radius: var(--x-radius) var(--x-radius) 0 0;
background: var(--x-card-header-bg);
// backdrop-filter: blur(5px);
// background: var(--x-card-header-bg);
padding: 1px;
// position: sticky;
// top: 0;
width: fit-content;
color: var(--x-card-header-fg);
font-size: 1rem;
white-space: nowrap;
}
.title {
font-weight: normal;
}
.body {
display: grid;
gap: var(--x-gutter-sm);
border-radius: 0 var(--x-radius) var(--x-radius) var(--x-radius);
background: var(--x-card-body-bg);
padding: var(--x-gutter);
}

View File

@@ -1,42 +0,0 @@
import { observer } from 'mobx-react-lite';
import type { FC, ReactNode } from 'react';
import { CardArrow } from './arrow.tsx';
import styles from './item.module.scss';
import { CardStore } from './store.ts';
const CardItemTitle: FC<{
id: string;
title: string;
}> = observer(({ id, title }) => {
const { disabledMoveUpId, disabledMoveDownId, moveCardDown, moveCardUp } =
CardStore;
return (
<h2 className={styles.header}>
<CardArrow
disabled={id === disabledMoveUpId}
handleClick={moveCardUp}
id={id}
isDown={false}
/>
<span className={styles.title}>{title}</span>
<CardArrow
disabled={id === disabledMoveDownId}
handleClick={moveCardDown}
id={id}
isDown
/>
</h2>
);
});
export const CardItem: FC<{
id: string;
title: string;
children: ReactNode;
}> = ({ id, title, children, ...props }) => {
return (
<div className={styles.main} id={id} {...props}>
<CardItemTitle id={id} title={title} />
<div className={styles.body}>{children}</div>
</div>
);
};

View File

@@ -1,5 +0,0 @@
.main {
&::before {
content: "👆 ";
}
}

View File

@@ -1,5 +0,0 @@
import type { AnchorHTMLAttributes, FC } from 'react';
import styles from './link.module.scss';
export const CardLink: FC<AnchorHTMLAttributes<HTMLAnchorElement>> = (
props
) => <a className={styles.main} target="_blank" {...props} />;

View File

@@ -1,5 +0,0 @@
import type { FC, HTMLProps } from 'react';
import styles from './multi-col.module.scss';
export const CardMultiColContainer: FC<HTMLProps<HTMLDivElement>> = (props) => (
<div className={styles.main} {...props} />
);

View File

@@ -1,5 +0,0 @@
import type { FC, HTMLProps } from 'react';
import styles from './single-col.module.scss';
export const CardSingleColContainer: FC<HTMLProps<HTMLDivElement>> = (
props
) => <div className={styles.main} {...props} />;

View File

@@ -1,147 +0,0 @@
import { configure, makeAutoObservable } from 'mobx';
import { DatabaseLoader } from '@/Components/Database/components/loader';
import { DiskUsageLoader } from '@/Components/DiskUsage/components/loader.ts';
import { MyInfoLoader } from '@/Components/MyInfo/components/loader.ts';
import { NetworkStatsLoader } from '@/Components/NetworkStats/components/loader.ts';
import { NodesLoader } from '@/Components/Nodes/components/loader.ts';
import { PhpExtensionsLoader } from '@/Components/PhpExtensions/components/loader.ts';
import { PhpInfoLoader } from '@/Components/PhpInfo/components/loader.ts';
import { PingLoader } from '@/Components/Ping/components/loader.ts';
import { PollStore } from '@/Components/Poll/components/store.ts';
import type { PollDataProps } from '@/Components/Poll/components/typings.ts';
import { ServerBenchmarkLoader } from '@/Components/ServerBenchmark/components/loader.ts';
import { ServerInfoLoader } from '@/Components/ServerInfo/components/loader.ts';
import { ServerStatusLoader } from '@/Components/ServerStatus/components/loader.ts';
import { TemperatureSensorLoader } from '@/Components/TemperatureSensor/components/loader.ts';
import type { CardProps } from './typings.ts';
configure({
enforceActions: 'observed',
});
export interface StoragePriorityItemProps {
id: string;
priority: number;
}
class Main {
cards: CardProps[] = [];
constructor() {
makeAutoObservable(this);
[
NodesLoader(),
TemperatureSensorLoader(),
ServerStatusLoader(),
NetworkStatsLoader(),
DiskUsageLoader(),
PingLoader(),
ServerInfoLoader(),
PhpInfoLoader(),
PhpExtensionsLoader(),
DatabaseLoader(),
MyInfoLoader(),
ServerBenchmarkLoader(),
].map((item) => {
return this.addCard(item);
});
}
addCard = (card: CardProps) => {
const priority = this.getStoragePriority(card.id);
if (priority) {
card.priority = priority;
}
this.cards.push(card);
};
get cardsLength() {
return this.cards.length;
}
get enabledCards(): CardProps[] {
const { pollData } = PollStore;
return this.cards
.filter(({ id }) => Boolean(pollData?.[id as keyof PollDataProps]))
.toSorted((a, b) => {
return a.priority - b.priority;
});
}
get enabledCardsLength(): number {
return this.enabledCards.length;
}
private setCardsPriority = (cards: CardProps[]) => {
for (const { id, priority } of cards) {
const i = this.cards.findIndex((item) => item.id === id);
if (i !== -1 && this.cards[i].priority !== priority) {
this.cards[i].priority = priority;
}
}
};
setCard = ({ id, ...card }: Partial<CardProps>) => {
const i = this.cards.findIndex((item) => item.id === id);
if (i === -1) {
return;
}
this.cards[i] = { ...this.cards[i], ...card };
};
moveCardUp = (id: string) => {
const cards = this.enabledCards;
const i = cards.findIndex((item) => item.id === id);
if (i <= 0) {
return;
}
[cards[i].priority, cards[i - 1].priority] = [
cards[i - 1].priority,
cards[i].priority,
];
this.setCardsPriority(cards);
this.setStoragePriorityItems();
};
moveCardDown = (id: string) => {
const cards = this.enabledCards;
const i = cards.findIndex((item) => item.id === id);
if (i === -1 || i === cards.length - 1) {
return;
}
[cards[i].priority, cards[i + 1].priority] = [
cards[i + 1].priority,
cards[i].priority,
];
this.setCardsPriority(cards);
this.setStoragePriorityItems();
};
private getStoragePriorityItems = (): StoragePriorityItemProps[] | null => {
const items = localStorage.getItem('cardsPriority');
if (!items) {
return null;
}
return (JSON.parse(items) as StoragePriorityItemProps[]) || null;
};
private setStoragePriorityItems = (): void => {
localStorage.setItem(
'cardsPriority',
JSON.stringify(
this.enabledCards.map(({ id, priority }) => ({ id, priority }))
)
);
};
getStoragePriority = (id: string): number => {
const items = this.getStoragePriorityItems();
if (!items) {
return 0;
}
const item = items.find((n) => n.id === id);
return item ? item.priority : 0;
};
get disabledMoveUpId(): string {
const items = this.enabledCards;
if (items.length <= 1) {
return '';
}
return items[0].id;
}
get disabledMoveDownId(): string {
const items = this.enabledCards;
if (items.length <= 1) {
return '';
}
return items.at(-1)?.id ?? '';
}
}
export const CardStore = new Main();

View File

@@ -1,9 +0,0 @@
import type { FC } from 'react';
export interface CardProps {
id: string;
title: string;
enabled?: boolean;
priority: number;
component: FC;
nav: FC;
}

View File

@@ -1,10 +1,10 @@
import { observer } from 'mobx-react-lite';
import { type FC, memo } from 'react';
import { CardGroup } from '@/Components/Card/components/group';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardMultiColContainer } from '@/Components/Card/components/multi-col-container.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { Alert } from '@/Components/Utils/components/alert';
import { UiMultiColContainer } from '@/Components/ui/col/multi-container.tsx';
import { DatabaseConstants } from './constants.ts';
import { DatabaseStore } from './store';
export const Database: FC = memo(
@@ -21,15 +21,15 @@ export const Database: FC = memo(
['PDO', pollData?.pdo ?? false],
];
return (
<CardItem id={DatabaseConstants.id} title={gettext('Database')}>
<CardMultiColContainer>
<ModuleItem id={DatabaseConstants.id} title={gettext('Database')}>
<UiMultiColContainer>
{shortItems.map(([name, content]) => (
<CardGroup key={name} label={name}>
<ModuleGroup key={name} label={name}>
<Alert isSuccess={Boolean(content)} msg={content} />
</CardGroup>
</ModuleGroup>
))}
</CardMultiColContainer>
</CardItem>
</UiMultiColContainer>
</ModuleItem>
);
})
);

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { DatabaseConstants } from './constants.ts';
import { Database as component } from './index.tsx';
import { Database as content } from './index.tsx';
import { DatabaseNav as nav } from './nav.tsx';
export const DatabaseLoader = (): CardProps => ({
export const DatabaseLoader: ModuleProps = {
id: DatabaseConstants.id,
title: gettext('Database'),
priority: 600,
component,
content,
nav,
});
};

View File

@@ -1,8 +1,8 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { Meter } from '@/Components/Meter/components/index.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { DiskUsageConstants } from './constants.ts';
import styles from './index.module.scss';
import { DiskUsageStore } from './store.ts';
@@ -13,7 +13,7 @@ export const DiskUsage: FC = observer(() => {
return null;
}
return (
<CardItem id={DiskUsageConstants.id} title={gettext('Disk Usage')}>
<ModuleItem id={DiskUsageConstants.id} title={gettext('Disk Usage')}>
<div className={styles.main}>
{items.map(({ id, free, total }) => (
<Meter
@@ -25,6 +25,6 @@ export const DiskUsage: FC = observer(() => {
/>
))}
</div>
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import { DiskUsage as component } from '.';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { DiskUsage as content } from '.';
import { DiskUsageConstants } from './constants';
import { DiskUsageNav as nav } from './nav';
export const DiskUsageLoader = (): CardProps => ({
export const DiskUsageLoader: ModuleProps = {
id: DiskUsageConstants.id,
title: gettext('Disk usage'),
priority: 250,
component,
content,
nav,
});
};

View File

@@ -1,25 +1,33 @@
import { ChevronDown, ChevronUp } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import { type FC, type MouseEvent, useCallback } from 'react';
import { gettext } from '@/Components/Language/index.ts';
import styles from './arrow.module.scss';
export const CardArrow: FC<{
import { ModuleStore } from './store.ts';
export const ModuleArrow: FC<{
isDown: boolean;
disabled: boolean;
id: string;
handleClick: (id: string) => void;
}> = ({ isDown, disabled, id, handleClick }) => {
}> = observer(({ isDown, id }) => {
const { disabledMoveUpId, disabledMoveDownId, moveUp, moveDown } =
ModuleStore;
const disabled = isDown ? disabledMoveDownId === id : disabledMoveUpId === id;
const handleMove = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
handleClick(id);
if (isDown) {
moveDown(id);
return;
}
moveUp(id);
},
[handleClick, id]
[isDown, moveDown, moveUp, id]
);
return (
<button
className={styles.arrow}
data-disabled={disabled || undefined}
disabled={disabled}
onClick={handleMove}
title={isDown ? gettext('Move down') : gettext('Move up')}
type="button"
@@ -27,4 +35,4 @@ export const CardArrow: FC<{
{isDown ? <ChevronDown /> : <ChevronUp />}
</button>
);
};
});

View File

@@ -1,15 +1,10 @@
import type { FC, ReactNode } from 'react';
import styles from './group.module.scss';
export interface CardGroupProps {
export const ModuleGroup: FC<{
label?: ReactNode;
children: ReactNode;
title?: string;
}
export const CardGroup: FC<CardGroupProps> = ({
label = '',
title = '',
children,
}) => (
}> = ({ label = '', title = '', children }) => (
<div className={styles.main}>
{Boolean(label) && (
<div className={styles.label} title={title}>

View File

@@ -0,0 +1,34 @@
import { observer } from 'mobx-react-lite';
import { type FC, useEffect } from 'react';
import { ModulePriority } from '@/Components/Module/components/priority.ts';
import styles from './index.module.scss';
import { ModulePreset } from './preset.ts';
import { ModuleStorage } from './storage.ts';
import { ModuleStore } from './store.ts';
import type { SortedModuleProps } from './typings.ts';
export const Modules: FC = observer(() => {
const { setSortedModules, availableModules } = ModuleStore;
useEffect(() => {
const storageItems = ModuleStorage.getItems();
const sorted: SortedModuleProps[] = [];
for (const preset of ModulePreset.items) {
sorted.push({
id: preset.id,
priority:
Number(storageItems?.[preset.id]) ||
ModulePriority.indexOf(preset.id),
});
}
setSortedModules(sorted);
}, [setSortedModules]);
if (!availableModules.length) {
return null;
}
return (
<div className={styles.container}>
{availableModules.map(({ id, content: C }) => {
return <C key={id} />;
})}
</div>
);
});

View File

@@ -0,0 +1,51 @@
@use "../../Style/components/device.scss" as m;
:root {
--x-module-bg: hsl(0 0% 0% / 0.95);
--x-module-header-bg: hsl(0 0% 100% / 0.75);
--x-module-header-fg: hsl(0 0% 0%);
--x-module-header-title-fg: hsl(0 0% 0% / 0.7);
--x-module-header-title-bg: hsl(0 0% 0% / 0.1);
--x-module-body-bg: var(--x-module-header-bg);
--x-module-box-shadow: hsla(0 0% 20% 0.3) 0px -1px 0px hsl(0 0% 100%) 0px 1px 0px inset,
hsla(0 0% 20% 0.3) 0px -1px 0px inset hsl(0 0% 100%) 0px 1px 0px;
@media (prefers-color-scheme: dark) {
--x-module-bg: hsl(0 0% 15% / 0.95);
--x-module-header-bg: hsl(0 0% 100% / 0.1);
--x-module-header-fg: hsl(0 0% 100% / 0.7);
--x-module-header-title-fg: hsl(0 0% 100% / 0.7);
--x-module-header-title-bg: hsl(0 0% 100% / 0.1);
--x-module-body-bg: var(--x-module-header-bg);
--x-module-box-shadow: 0px 0px 0px 1px hsl(0 0% 0%) inset;
}
}
.main {
position: relative;
flex-grow: 1;
scroll-margin-top: 0;
}
.header {
display: flex;
align-items: center;
// z-index: 10;
border-radius: var(--x-radius) var(--x-radius) 0 0;
background: var(--x-module-header-bg);
// backdrop-filter: blur(5px);
// background: var(--x-module-header-bg);
padding: 1px;
// position: sticky;
// top: 0;
width: fit-content;
color: var(--x-module-header-fg);
font-size: 1rem;
white-space: nowrap;
}
.title {
font-weight: normal;
}
.body {
display: grid;
gap: var(--x-gutter-sm);
border-radius: 0 var(--x-radius) var(--x-radius) var(--x-radius);
background: var(--x-module-body-bg);
padding: var(--x-gutter);
}

View File

@@ -0,0 +1,28 @@
import type { FC, ReactNode } from 'react';
import { ModuleArrow } from '@/Components/Module/components/arrow.tsx';
import styles from './item.module.scss';
const ModuleItemTitle: FC<{
id: string;
title: string;
}> = ({ id, title }) => {
return (
<h2 className={styles.header}>
<ModuleArrow id={id} isDown={false} />
<span className={styles.title}>{title}</span>
<ModuleArrow id={id} isDown />
</h2>
);
};
export const ModuleItem: FC<{
id: string;
title: string;
children: ReactNode;
}> = ({ id, title, children, ...props }) => {
return (
<div className={styles.main} id={id} {...props}>
<ModuleItemTitle id={id} title={title} />
<div className={styles.body}>{children}</div>
</div>
);
};

View File

@@ -0,0 +1,29 @@
import { DatabaseLoader } from '@/Components/Database/components/loader.ts';
import { DiskUsageLoader } from '@/Components/DiskUsage/components/loader.ts';
import { MyInfoLoader } from '@/Components/MyInfo/components/loader.ts';
import { NetworkStatsLoader } from '@/Components/NetworkStats/components/loader.ts';
import { NodesLoader } from '@/Components/Nodes/components/loader.ts';
import { PhpExtensionsLoader } from '@/Components/PhpExtensions/components/loader.ts';
import { PhpInfoLoader } from '@/Components/PhpInfo/components/loader.ts';
import { PingLoader } from '@/Components/Ping/components/loader.ts';
import { ServerBenchmarkLoader } from '@/Components/ServerBenchmark/components/loader.ts';
import { ServerInfoLoader } from '@/Components/ServerInfo/components/loader.ts';
import { ServerStatusLoader } from '@/Components/ServerStatus/components/loader.ts';
import { TemperatureSensorLoader } from '@/Components/TemperatureSensor/components/loader.ts';
export const ModulePreset = {
items: [
NodesLoader,
TemperatureSensorLoader,
ServerStatusLoader,
NetworkStatsLoader,
DiskUsageLoader,
PingLoader,
ServerInfoLoader,
PhpInfoLoader,
PhpExtensionsLoader,
DatabaseLoader,
MyInfoLoader,
ServerBenchmarkLoader,
],
};

View File

@@ -0,0 +1,27 @@
import { PingConstants } from '@/Components/Ping/components/constants.ts';
import { DatabaseConstants } from '../../Database/components/constants.ts';
import { DiskUsageConstants } from '../../DiskUsage/components/constants.ts';
import { MyInfoConstants } from '../../MyInfo/components/constants.ts';
import { NetworkStatsConstants } from '../../NetworkStats/components/constants.ts';
import { NodesConstants } from '../../Nodes/components/constants.ts';
import { PhpExtensionsConstants } from '../../PhpExtensions/components/constants.ts';
import { PhpInfoConstants } from '../../PhpInfo/components/constants.ts';
import { ServerBenchmarkConstants } from '../../ServerBenchmark/components/constants.ts';
import { ServerInfoConstants } from '../../ServerInfo/components/constants.ts';
import { ServerStatusConstants } from '../../ServerStatus/components/constants.ts';
import { TemperatureSensorConstants } from '../../TemperatureSensor/components/constants.ts';
export const ModulePriority = [
NodesConstants.id,
TemperatureSensorConstants.id,
ServerStatusConstants.id,
NetworkStatsConstants.id,
DiskUsageConstants.id,
ServerInfoConstants.id,
PingConstants.id,
PhpInfoConstants.id,
PhpExtensionsConstants.id,
DatabaseConstants.id,
ServerBenchmarkConstants.id,
MyInfoConstants.id,
];

View File

@@ -0,0 +1,27 @@
import type { StoragePriorityItemProps } from './store.ts';
const STORAGE_KEY = 'module-priority';
export const ModuleStorage = {
getItems(): Record<string, number> {
const items = localStorage.getItem(STORAGE_KEY);
if (!items) {
return {};
}
try {
return JSON.parse(items) as Record<string, number>;
} catch {
return {};
}
},
setItems(items: Record<string, number>) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
},
getPriority(id: string): number {
return this.getItems()[id] || 0;
},
setPriority({ id, priority }: StoragePriorityItemProps) {
const items = this.getItems();
items[id] = priority;
this.setItems(items);
},
};

View File

@@ -0,0 +1,84 @@
import { configure, makeAutoObservable } from 'mobx';
import { ModulePriority } from '@/Components/Module/components/priority.ts';
import { PollStore } from '@/Components/Poll/components/store.ts';
import type { PollDataProps } from '@/Components/Poll/components/typings.ts';
import { ModulePreset } from './preset.ts';
import { ModuleStorage } from './storage.ts';
import type { ModuleProps, SortedModuleProps } from './typings.ts';
configure({
enforceActions: 'observed',
});
export interface StoragePriorityItemProps {
id: string;
priority: number;
}
const saveSortedStorage = (items: SortedModuleProps[]) => {
const sorted: Record<string, number> = {};
for (const item of items) {
sorted[item.id] = item.priority;
}
ModuleStorage.setItems(sorted);
};
class Main {
sortedModules: SortedModuleProps[] = [];
constructor() {
makeAutoObservable(this);
}
setSortedModules = (modules: SortedModuleProps[]) => {
this.sortedModules = modules.toSorted((a, b) => {
return a.priority - b.priority;
});
};
get availableModules(): ModuleProps[] {
const { pollData } = PollStore;
const items = ModulePreset.items
.filter(({ id }) => Boolean(pollData?.[id as keyof PollDataProps]))
.toSorted((a, b) => {
const moduleA = this.sortedModules.find((item) => item.id === a.id);
const moduleB = this.sortedModules.find((item) => item.id === b.id);
return (
Number(moduleA?.priority ?? ModulePriority.indexOf(a.id)) -
Number(moduleB?.priority ?? ModulePriority.indexOf(b.id))
);
});
return items;
}
moveUp = (id: string) => {
const i = this.sortedModules.findIndex((item) => item.id === id);
if (i === 0) {
return;
}
[this.sortedModules[i].priority, this.sortedModules[i - 1].priority] = [
this.sortedModules[i - 1].priority,
this.sortedModules[i].priority,
];
saveSortedStorage(this.sortedModules);
};
moveDown = (id: string) => {
const i = this.sortedModules.findIndex((item) => item.id === id);
if (i === this.sortedModules.length - 1) {
return;
}
[this.sortedModules[i].priority, this.sortedModules[i + 1].priority] = [
this.sortedModules[i + 1].priority,
this.sortedModules[i].priority,
];
saveSortedStorage(this.sortedModules);
};
get disabledMoveUpId(): string {
const items = this.availableModules;
if (items.length <= 1) {
return '';
}
return items[0].id;
}
get disabledMoveDownId(): string {
const items = this.availableModules;
if (items.length <= 1) {
return '';
}
return items.at(-1)?.id ?? '';
}
}
export const ModuleStore = new Main();

View File

@@ -0,0 +1,11 @@
import type { FC } from 'react';
export interface ModuleProps {
id: string;
content: FC;
nav: FC;
}
export interface SortedModuleProps {
id: string;
priority: number;
}

View File

@@ -1,11 +1,11 @@
import { observer } from 'mobx-react-lite';
import type { FC, ReactNode } from 'react';
import { CardGroup } from '@/Components/Card/components/group.tsx';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { Location } from '@/Components/Location/components/index.tsx';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { useIp } from '@/Components/Utils/components/use-ip.ts';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import { MyInfoConstants } from './constants.ts';
import { MyInfoStore } from './store.ts';
export const MyInfo: FC = observer(() => {
@@ -44,14 +44,14 @@ export const MyInfo: FC = observer(() => {
return null;
}
return (
<CardItem id={MyInfoConstants.id} title={gettext('My Info')}>
<CardSingleColContainer>
<ModuleItem id={MyInfoConstants.id} title={gettext('My Info')}>
<UiSingleColContainer>
{items.map(([name, content]) => (
<CardGroup key={name} label={name}>
<ModuleGroup key={name} label={name}>
{content}
</CardGroup>
</ModuleGroup>
))}
</CardSingleColContainer>
</CardItem>
</UiSingleColContainer>
</ModuleItem>
);
});

View File

@@ -1,14 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { MyInfoConstants } from './constants.ts';
import { MyInfo as component } from './index.tsx';
import { MyInfo as content } from './index.tsx';
import { MyInfoNav as nav } from './nav';
export const MyInfoLoader = (): CardProps => {
return {
id: MyInfoConstants.id,
title: gettext('My Information'),
priority: 900,
component,
nav,
};
export const MyInfoLoader: ModuleProps = {
id: MyInfoConstants.id,
content,
nav,
};

View File

@@ -1,14 +1,11 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { CardStore } from '@/Components/Card/components/store.ts';
import { ModuleStore } from '@/Components/Module/components/store';
import styles from './index.module.scss';
export const Nav: FC = observer(() => {
const { enabledCards } = CardStore;
const { availableModules } = ModuleStore;
// const { activeIndex } = NavStore;
const items = enabledCards.map(({ id, nav: Component, enabled = true }) => {
if (!enabled) {
return null;
}
const items = availableModules.map(({ id, nav: Component }) => {
return <Component key={id} />;
});
// .filter((n) => n) as ReactElement[];

View File

@@ -1,7 +1,7 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { usePrevious } from '@/Components/Utils/components/use-previous.ts';
import { NetworkStatsConstants } from './constants.ts';
import styles from './index.module.scss';
@@ -18,7 +18,7 @@ export const NetworkStats: FC = observer(() => {
}
const seconds = timestamp - (prevData?.timestamp || timestamp);
return (
<CardItem id={NetworkStatsConstants.id} title={gettext('Network Stats')}>
<ModuleItem id={NetworkStatsConstants.id} title={gettext('Network Stats')}>
<div className={styles.container}>
{sortNetworks.map(({ id, rx, tx }) => {
if (!(rx || tx)) {
@@ -41,6 +41,6 @@ export const NetworkStats: FC = observer(() => {
);
})}
</div>
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,13 +1,10 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { NetworkStatsConstants } from './constants.ts';
import { NetworkStats as component } from './index.tsx';
import { NetworkStats as content } from './index.tsx';
import { NetworkStatsNav as nav } from './nav.tsx';
export const NetworkStatsLoader = (): CardProps => ({
export const NetworkStatsLoader: ModuleProps = {
id: NetworkStatsConstants.id,
title: gettext('Network Stats'),
priority: 200,
component,
content,
nav,
});
};

View File

@@ -15,11 +15,15 @@ final class NodesAction extends NodesApi
$nodeId = filter_input(\INPUT_GET, 'nodeId', \FILTER_DEFAULT);
$response = new RestResponse();
if ( ! $nodeId) {
$response->setStatus(StatusCode::$BAD_REQUEST)->end();
$response
->setStatus(StatusCode::$BAD_REQUEST)
->end();
}
$data = $this->getNodeData($nodeId);
if ( ! $data) {
$response->setStatus(StatusCode::$NO_CONTENT)->end();
$response
->setStatus(StatusCode::$NO_CONTENT)
->end();
}
$response
->setData($data)

View File

@@ -1,7 +1,7 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { NodesConstants } from './constants.ts';
import styles from './index.module.scss';
import { Node } from './node.tsx';
@@ -13,12 +13,12 @@ export const Nodes: FC = observer(() => {
return null;
}
return (
<CardItem id={NodesConstants.id} title={gettext('Nodes')}>
<ModuleItem id={NodesConstants.id} title={gettext('Nodes')}>
<div className={styles.main}>
{nodeIds.map((id) => (
<Node id={id} key={id} />
))}
</div>
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { NodesConstants } from './constants.ts';
import { Nodes as component } from './index.tsx';
import { Nodes as content } from './index.tsx';
import { NodesNav as nav } from './nav.tsx';
export const NodesLoader = (): CardProps => ({
export const NodesLoader: ModuleProps = {
id: NodesConstants.id,
title: gettext('Nodes'),
priority: 90,
component,
content,
nav,
});
};

View File

@@ -1,11 +1,11 @@
import { type FC, memo, useEffect, useState } from 'react';
import { CardError } from '@/Components/Card/components/error.tsx';
import { serverFetch } from '@/Components/Fetch/server-fetch.ts';
import { gettext } from '@/Components/Language/index.ts';
import { Placeholder } from '@/Components/Placeholder/index.tsx';
import type { PollDataProps } from '@/Components/Poll/components/typings.ts';
import { OK } from '@/Components/Rest/http-status.ts';
import { template } from '@/Components/Utils/components/template.ts';
import { UiError } from '@/Components/ui/error/index.tsx';
import { NodesCpu } from './cpu.tsx';
import { NodesDisk } from './disk.tsx';
import { NodesNetworkStats } from './network.tsx';
@@ -55,9 +55,7 @@ export const Node: FC<{ id: string }> = memo(({ id }) => {
<div className={styles.main}>
<header className={styles.name}>{id}</header>
{error !== 0 && (
<CardError>
{template(gettext('Error: {{error}}'), { error })}
</CardError>
<UiError>{template(gettext('Error: {{error}}'), { error })}</UiError>
)}
{loading && <Placeholder height={10} />}
{!loading && serverStatus && (

View File

@@ -1,12 +1,12 @@
import { observer } from 'mobx-react-lite';
import { type FC, memo } from 'react';
import { CardGroup } from '@/Components/Card/components/group.tsx';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardMultiColContainer } from '@/Components/Card/components/multi-col-container.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { Alert } from '@/Components/Utils/components/alert';
import { SearchLink } from '@/Components/Utils/components/search-link';
import { UiMultiColContainer } from '@/Components/ui/col/multi-container.tsx';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import { PhpExtensionsConstants } from './constants.ts';
import { PhpExtensionsStore } from './store.ts';
export const PhpExtensions: FC = memo(
@@ -65,27 +65,27 @@ export const PhpExtensions: FC = memo(
return 0;
});
return (
<CardItem
<ModuleItem
id={PhpExtensionsConstants.id}
title={gettext('PHP Extensions')}
>
<CardMultiColContainer>
<UiMultiColContainer>
{shortItems.map(([name, enabled]) => (
<CardGroup key={name} label={name}>
<ModuleGroup key={name} label={name}>
<Alert isSuccess={enabled} />
</CardGroup>
</ModuleGroup>
))}
</CardMultiColContainer>
<CardSingleColContainer>
</UiMultiColContainer>
<UiSingleColContainer>
{Boolean(longItems.length) && (
<CardGroup label={gettext('Loaded extensions')}>
<ModuleGroup label={gettext('Loaded extensions')}>
{longItems.map((id) => (
<SearchLink key={id} keyword={id} />
))}
</CardGroup>
</ModuleGroup>
)}
</CardSingleColContainer>
</CardItem>
</UiSingleColContainer>
</ModuleItem>
);
})
);

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { PhpExtensionsConstants } from './constants.ts';
import { PhpExtensions as component } from './index.tsx';
import { PhpExtensions as content } from './index.tsx';
import { PhpExtensionsNav as nav } from './nav.tsx';
export const PhpExtensionsLoader = (): CardProps => ({
export const PhpExtensionsLoader: ModuleProps = {
id: PhpExtensionsConstants.id,
title: gettext('PHP Extensions'),
priority: 500,
component,
content,
nav,
});
};

View File

@@ -1,14 +1,14 @@
import { observer } from 'mobx-react-lite';
import { type FC, memo, type ReactNode } from 'react';
import { Link } from '@/Components/Button/components/index.tsx';
import { CardGroup } from '@/Components/Card/components/group';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardMultiColContainer } from '@/Components/Card/components/multi-col-container.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { serverFetchRoute } from '@/Components/Fetch/server-fetch.ts';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { Alert } from '@/Components/Utils/components/alert';
import { SearchLink } from '@/Components/Utils/components/search-link';
import { UiMultiColContainer } from '@/Components/ui/col/multi-container.tsx';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import { PhpInfoConstants } from './constants.ts';
import { PhpInfoPhpVersion } from './php-version';
import { PhpInfoStore } from './store.ts';
@@ -72,27 +72,27 @@ export const PhpInfo: FC = memo(
],
];
return (
<CardItem id={PhpInfoConstants.id} title={gettext('PHP Information')}>
<CardMultiColContainer>
<ModuleItem id={PhpInfoConstants.id} title={gettext('PHP Information')}>
<UiMultiColContainer>
{oneLineItems.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
{shortItems.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
</CardMultiColContainer>
<CardSingleColContainer>
</UiMultiColContainer>
<UiSingleColContainer>
{longItems.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
</CardSingleColContainer>
</CardItem>
</UiSingleColContainer>
</ModuleItem>
);
})
);

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { PhpInfoConstants } from './constants.ts';
import { PhpInfo as component } from './index.tsx';
import { PhpInfo as content } from './index.tsx';
import { PhpInfoNav as nav } from './nav.tsx';
export const PhpInfoLoader = (): CardProps => ({
export const PhpInfoLoader: ModuleProps = {
id: PhpInfoConstants.id,
title: gettext('PHP Information'),
priority: 400,
component,
content,
nav,
});
};

View File

@@ -1,12 +1,12 @@
import { type FC, memo } from 'react';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { PingConstants } from './constants.ts';
import { PingServerToBrowser } from './server-browser.tsx';
export const Ping: FC = memo(() => {
return (
<CardItem id={PingConstants.id} title={gettext('Ping')}>
<ModuleItem id={PingConstants.id} title={gettext('Ping')}>
<PingServerToBrowser />
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,13 +1,10 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { PingConstants } from './constants.ts';
import { Ping as component } from './index.tsx';
import { Ping as content } from './index.tsx';
import { PingNav as nav } from './nav.tsx';
export const PingLoader = (): CardProps => ({
export const PingLoader: ModuleProps = {
id: PingConstants.id,
title: gettext('Network Ping'),
priority: 250,
component,
content,
nav,
});
};

View File

@@ -2,13 +2,13 @@ import { observer } from 'mobx-react-lite';
import { type FC, useCallback, useRef } from 'react';
import { Button } from '@/Components/Button/components/index.tsx';
import { ButtonStatus } from '@/Components/Button/components/typings.ts';
import { CardGroup } from '@/Components/Card/components/group.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { serverFetch } from '@/Components/Fetch/server-fetch.ts';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { OK } from '@/Components/Rest/http-status.ts';
import { calculateMdev } from '@/Components/Utils/components/mdev.ts';
import { template } from '@/Components/Utils/components/template.ts';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import type { ServerToBrowserPingItemProps } from '../typings.ts';
import { PingStore } from './store.ts';
import styles from './style.module.scss';
@@ -98,16 +98,16 @@ export const PingServerToBrowser: FC = observer(() => {
]);
const count = serverToBrowserPingItems.length;
return (
<CardSingleColContainer>
<CardGroup label={gettext('Server ⇄ Browser')}>
<UiSingleColContainer>
<ModuleGroup label={gettext('Server ⇄ Browser')}>
<Button
onClick={handlePing}
status={isPing ? ButtonStatus.Loading : ButtonStatus.Pointer}
>
{isPing ? gettext('Stop ping') : gettext('Start ping')}
</Button>
</CardGroup>
<CardGroup label={gettext('Results')}>
</ModuleGroup>
<ModuleGroup label={gettext('Results')}>
<div className={styles.resultContainer}>
{!count && '-'}
{Boolean(count) && (
@@ -117,7 +117,7 @@ export const PingServerToBrowser: FC = observer(() => {
)}
{Boolean(count) && <Results />}
</div>
</CardGroup>
</CardSingleColContainer>
</ModuleGroup>
</UiSingleColContainer>
);
});

View File

@@ -24,6 +24,8 @@ final class PollAction extends PoolConstants
'ServerStatus\\ServerStatusPoll',
'ServerInfo\\ServerInfoPoll',
'Nodes\\NodesPoll',
'TemperatureSensor\\TemperatureSensorPoll',
'ServerBenchmark\\ServerBenchmarkPoll',
] as $fn) {
$class = "\\InnStudio\\Prober\\Components\\{$fn}";
$data = array_merge($data, (new $class())->render());

View File

@@ -8,6 +8,7 @@ import type { PhpExtensionsPollDataProps } from '@/Components/PhpExtensions/comp
import type { PhpInfoPollDataProps } from '@/Components/PhpInfo/components/typings.ts';
import type { ServerInfoPollDataProps } from '@/Components/ServerInfo/components/typings.ts';
import type { ServerStatusPollDataProps } from '@/Components/ServerStatus/components/typings.ts';
import type { TemperatureSensorPollDataProps } from '@/Components/TemperatureSensor/components/typings.ts';
import type { UserConfigProps } from '@/Components/UserConfig/typings.ts';
export interface PollDataProps {
@@ -22,4 +23,5 @@ export interface PollDataProps {
serverStatus: ServerStatusPollDataProps | null;
serverInfo: ServerInfoPollDataProps | null;
nodes: NodesPollDataProps | null;
temperatureSensor: TemperatureSensorPollDataProps | null;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace InnStudio\Prober\Components\ServerBenchmark;
use InnStudio\Prober\Components\UserConfig\UserConfigApi;
final class ServerBenchmarkPoll extends ServerBenchmarkConstants
{
public function render()
{
if (UserConfigApi::isDisabled($this->ID)) {
return [
$this->ID => null,
];
}
return [
$this->ID => true,
];
}
}

View File

@@ -1,16 +1,16 @@
import { type FC, memo } from 'react';
import { CardDescription } from '@/Components/Card/components/description.tsx';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { UiDescription } from '@/Components/ui/description/index.tsx';
import { ServerBenchmarkConstants } from './constants.ts';
import { ServerBenchmarkServers } from './servers.tsx';
export const ServerBenchmark: FC = memo(() => {
return (
<CardItem
<ModuleItem
id={ServerBenchmarkConstants.id}
title={gettext('Server Benchmark')}
>
<CardDescription
<UiDescription
items={[
{
id: 'serverBenchmarkTos',
@@ -21,6 +21,6 @@ export const ServerBenchmark: FC = memo(() => {
]}
/>
<ServerBenchmarkServers />
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { ServerBenchmarkConstants } from './constants.ts';
import { ServerBenchmark as component } from './index.tsx';
import { ServerBenchmark as content } from './index.tsx';
import { ServerBenchmarkNav as nav } from './nav.tsx';
export const ServerBenchmarkLoader = (): CardProps => ({
export const ServerBenchmarkLoader: ModuleProps = {
id: ServerBenchmarkConstants.id,
title: gettext('Server Benchmark'),
priority: 800,
component,
content,
nav,
});
};

View File

@@ -1,8 +1,8 @@
import copyToClipboard from 'copy-to-clipboard';
import type { FC, MouseEvent, ReactNode } from 'react';
import { CardRuby } from '@/Components/Card/components/ruby.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { template } from '@/Components/Utils/components/template.ts';
import { UiRuby } from '@/Components/ui/ruby/index.tsx';
import { ServerBenchmarkMarksMeter } from './marks-meter.tsx';
import styles from './server-item.module.scss';
import type { ServerBenchmarkMarksProps } from './typings.ts';
@@ -40,13 +40,13 @@ const ServerBenchmarkResult: FC<{
title={gettext('Touch to copy marks')}
type="button"
>
<CardRuby rt="CPU" ruby={cpuString} />
<UiRuby rt="CPU" ruby={cpuString} />
{sign}
<CardRuby rt={gettext('Read')} ruby={readString} />
<UiRuby rt={gettext('Read')} ruby={readString} />
{sign}
<CardRuby rt={gettext('Write')} ruby={writeString} />
<UiRuby rt={gettext('Write')} ruby={writeString} />
<span className={styles.sign}>=</span>
<CardRuby isResult rt={date || ''} ruby={totalString} />
<UiRuby isResult rt={date || ''} ruby={totalString} />
</button>
);
};

View File

@@ -1,11 +1,11 @@
import { DownloadCloud, Link } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import { type FC, useEffect, useState } from 'react';
import { CardError } from '@/Components/Card/components/error.tsx';
import { serverFetch } from '@/Components/Fetch/server-fetch.ts';
import { gettext } from '@/Components/Language/index.ts';
import { Placeholder } from '@/Components/Placeholder/index.tsx';
import { OK } from '@/Components/Rest/http-status.ts';
import { UiError } from '@/Components/ui/error/index.tsx';
import styles from './index.module.scss';
import { ServerBenchmarkMyServer } from './my-server.tsx';
import stylesItem from './server-item.module.scss';
@@ -114,9 +114,7 @@ export const ServerBenchmarkServers: FC = observer(() => {
[...new Array(5)].map((_, index) => <Placeholder key={index} />)
: results}
{error && (
<CardError>
{gettext('Can not fetch marks data from GitHub.')}
</CardError>
<UiError>{gettext('Can not fetch marks data from GitHub.')}</UiError>
)}
</div>
);

View File

@@ -1,14 +1,14 @@
import { observer } from 'mobx-react-lite';
import { type FC, memo, type ReactNode, useEffect } from 'react';
import { CardGroup } from '@/Components/Card/components/group.tsx';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardMultiColContainer } from '@/Components/Card/components/multi-col-container.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { serverFetch } from '@/Components/Fetch/server-fetch.ts';
import { gettext } from '@/Components/Language/index.ts';
import { Location } from '@/Components/Location/components/index.tsx';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { OK } from '@/Components/Rest/http-status.ts';
import { template } from '@/Components/Utils/components/template';
import { UiMultiColContainer } from '@/Components/ui/col/multi-container.tsx';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import { ServerInfoConstants } from './constants.ts';
import { ServerInfoStore } from './store.ts';
import type { ServerInfoPollDataProps } from './typings.ts';
@@ -29,9 +29,9 @@ const ServerTime: FC<{
return (
<>
{items.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
</>
);
@@ -52,13 +52,13 @@ const SingleItems: FC<{
[gettext('Script path'), scriptPath ?? gettext('Unavailable')],
];
return (
<CardSingleColContainer>
<UiSingleColContainer>
{items.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
</CardSingleColContainer>
</UiSingleColContainer>
);
});
const MultiItems: FC<{
@@ -88,9 +88,9 @@ const MultiItems: FC<{
return (
<>
{items.map(([title, content]) => (
<CardGroup key={title} label={title}>
<ModuleGroup key={title} label={title}>
{content}
</CardGroup>
</ModuleGroup>
))}
</>
);
@@ -127,8 +127,8 @@ export const ServerInfo: FC = observer(() => {
return null;
}
return (
<CardItem id={ServerInfoConstants.id} title={gettext('Server Info')}>
<CardMultiColContainer>
<ModuleItem id={ServerInfoConstants.id} title={gettext('Server Info')}>
<UiMultiColContainer>
<ServerTime
serverTime={pollData.serverTime}
serverUptime={pollData.serverUptime}
@@ -141,13 +141,13 @@ export const ServerInfo: FC = observer(() => {
serverName={pollData.serverName}
serverSoftware={pollData.serverSoftware}
/>
</CardMultiColContainer>
</UiMultiColContainer>
<SingleItems
cpuModel={pollData.cpuModel}
publicIpv4={publicIpv4}
scriptPath={pollData.scriptPath}
serverOs={pollData.serverOs}
/>
</CardItem>
</ModuleItem>
);
});

View File

@@ -1,13 +1,10 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { ServerInfoConstants } from './constants.ts';
import { ServerInfo as component } from './index.tsx';
import { ServerInfo as content } from './index.tsx';
import { ServerInfoNav as nav } from './nav.tsx';
export const ServerInfoLoader = (): CardProps => ({
export const ServerInfoLoader: ModuleProps = {
id: ServerInfoConstants.id,
title: gettext('Server Information'),
priority: 300,
component,
content,
nav,
});
};

View File

@@ -1,6 +1,6 @@
import type { FC } from 'react';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { ServerStatusConstants } from './constants.ts';
import styles from './index.module.scss';
import { MemBuffers } from './mem-buffers';
@@ -10,7 +10,7 @@ import { SwapCached } from './swap-cached';
import { SwapUsage } from './swap-usage';
import { SystemLoad } from './system-load';
export const ServerStatus: FC = () => (
<CardItem id={ServerStatusConstants.id} title={gettext('Server Status')}>
<ModuleItem id={ServerStatusConstants.id} title={gettext('Server Status')}>
<div className={styles.main}>
<div className={styles.modules}>
<SystemLoad />
@@ -21,5 +21,5 @@ export const ServerStatus: FC = () => (
<SwapCached />
</div>
</div>
</CardItem>
</ModuleItem>
);

View File

@@ -1,12 +1,9 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { ServerStatusConstants } from './constants.ts';
import { ServerStatus as component } from './index.tsx';
import { ServerStatus as content } from './index.tsx';
import { ServerStatusNav as nav } from './nav.tsx';
export const ServerStatusLoader = (): CardProps => ({
export const ServerStatusLoader: ModuleProps = {
id: ServerStatusConstants.id,
title: gettext('Server Status'),
priority: 100,
component,
content,
nav,
});
};

View File

@@ -0,0 +1,8 @@
<?php
namespace InnStudio\Prober\Components\TemperatureSensor;
class TemperatureSensorConstants
{
public $ID = 'temperatureSensor';
}

View File

@@ -4,44 +4,51 @@ namespace InnStudio\Prober\Components\TemperatureSensor;
use Exception;
use InnStudio\Prober\Components\Config\ConfigApi;
use InnStudio\Prober\Components\Events\EventsApi;
use InnStudio\Prober\Components\Rest\RestResponse;
use InnStudio\Prober\Components\Rest\StatusCode;
use InnStudio\Prober\Components\UserConfig\UserConfigApi;
final class TemperatureSensor
final class TemperatureSensorPoll extends TemperatureSensorConstants
{
public function __construct()
public function render()
{
EventsApi::on('init', function ($action) {
if ('temperature-sensor' !== $action) {
return $action;
}
$response = new RestResponse();
$items = $this->getItems();
if ($items) {
$response
->setData($items)
->end();
}
$cpuTemp = $this->getCpuTemp();
if ( ! $cpuTemp) {
$response->setStatus(StatusCode::$NO_CONTENT);
}
$items[] = [
'id' => 'cpu',
'name' => 'CPU',
'celsius' => round((float) $cpuTemp / 1000, 2),
if (UserConfigApi::isDisabled($this->ID)) {
return [
$this->ID => null,
];
$response
->setData($items)
->end();
});
}
$response = new RestResponse();
$items = $this->getItems();
if ( ! $items) {
return [
$this->ID => null,
];
}
if ($items) {
return [
$this->ID => $items,
];
}
$cpuTemp = $this->getCpuTemp();
if ( ! $cpuTemp) {
return [
$this->ID => null,
];
}
$items[] = [
'id' => 'cpu',
'name' => 'CPU',
'celsius' => round((float) $cpuTemp / 1000, 2),
];
return [
$this->ID => $items,
];
}
private function curl($url)
{
if ( ! \function_exists('curl_init')) {
return;
return (string) file_get_contents($url);
}
$ch = curl_init();
curl_setopt_array($ch, [

View File

@@ -1,26 +1,27 @@
import { observer } from 'mobx-react-lite';
import type { FC } from 'react';
import { CardGroup } from '@/Components/Card/components/group';
import { CardItem } from '@/Components/Card/components/item.tsx';
import { CardSingleColContainer } from '@/Components/Card/components/single-col-container.tsx';
import { gettext } from '@/Components/Language/index.ts';
import { Meter } from '@/Components/Meter/components';
import { ModuleGroup } from '@/Components/Module/components/group.tsx';
import { ModuleItem } from '@/Components/Module/components/item.tsx';
import { template } from '@/Components/Utils/components/template';
import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx';
import { TemperatureSensorConstants } from './constants.ts';
import { TemperatureSensorStore } from './store.ts';
export const TemperatureSensor: FC = observer(() => {
const { itemsCount, items } = TemperatureSensorStore;
if (!itemsCount) {
const { pollData } = TemperatureSensorStore;
if (!pollData?.items?.length) {
return null;
}
const { items } = pollData;
return (
<CardItem
<ModuleItem
id={TemperatureSensorConstants.id}
title={gettext('Templerature sensor')}
>
<CardSingleColContainer>
<UiSingleColContainer>
{items.map(({ id, name, celsius }) => (
<CardGroup
<ModuleGroup
key={id}
title={template(gettext('{{sensor}} temperature'), {
sensor: name,
@@ -32,9 +33,9 @@ export const TemperatureSensor: FC = observer(() => {
percentTag="℃"
value={celsius}
/>
</CardGroup>
</ModuleGroup>
))}
</CardSingleColContainer>
</CardItem>
</UiSingleColContainer>
</ModuleItem>
);
});

View File

@@ -1,15 +1,10 @@
import type { CardProps } from '@/Components/Card/components/typings.ts';
import { gettext } from '@/Components/Language/index.ts';
import type { ModuleProps } from '@/Components/Module/components/typings.ts';
import { TemperatureSensorConstants } from './constants.ts';
import { TemperatureSensor as component } from './index.tsx';
import { TemperatureSensor as content } from './index.tsx';
import { TemperatureSensorNav as nav } from './nav.tsx';
export const TemperatureSensorLoader = (): CardProps => {
return {
id: TemperatureSensorConstants.id,
title: gettext('Temperature sensor'),
priority: 100,
component,
nav,
};
export const TemperatureSensorLoader: ModuleProps = {
id: TemperatureSensorConstants.id,
content,
nav,
};

View File

@@ -6,8 +6,12 @@ import { TemperatureSensorConstants } from './constants.ts';
import { TemperatureSensorStore } from './store.ts';
export const TemperatureSensorNav: FC = observer(() => {
const { itemsCount } = TemperatureSensorStore;
if (!itemsCount) {
const { pollData } = TemperatureSensorStore;
if (!pollData?.items?.length) {
return null;
}
const { items } = pollData;
if (!items.length) {
return null;
}
return (

View File

@@ -1,49 +1,24 @@
import { configure, makeAutoObservable } from 'mobx';
import { CardStore } from '@/Components/Card/components/store.ts';
import { serverFetch } from '@/Components/Fetch/server-fetch.ts';
import { OK } from '@/Components/Rest/http-status.ts';
import { TemperatureSensorConstants } from './constants.ts';
import type { TemperatureSensorItemProps } from './typings.ts';
import { isDeepEqual } from '@/Components/Utils/components/is-deep-equal/index.ts';
import type { TemperatureSensorPollDataProps } from './typings.ts';
configure({
enforceActions: 'observed',
});
const { id } = TemperatureSensorConstants;
class Main {
items: TemperatureSensorItemProps[] = [];
pollData: TemperatureSensorPollDataProps | null = null;
latestPhpVersion = '';
constructor() {
makeAutoObservable(this);
}
setItems = (items: TemperatureSensorItemProps[]) => {
this.items = items;
};
private setEnabledCard = (): void => {
const { setCard, cards } = CardStore;
const item = cards.find((n) => n.id === id);
if (!item) {
setPollData = (pollData: TemperatureSensorPollDataProps | null) => {
if (isDeepEqual(pollData, this.pollData)) {
return;
}
if (item.enabled) {
return;
}
setCard({
id,
enabled: true,
});
this.pollData = pollData;
};
fetch = async () => {
const { data: items, status } =
await serverFetch<TemperatureSensorItemProps[]>('temperature-sensor');
if (items?.length && status === OK) {
this.setItems(items);
this.setEnabledCard();
setTimeout(() => {
this.fetch();
}, 1000);
}
setLatestPhpVersion = (latestPhpVersion: string) => {
this.latestPhpVersion = latestPhpVersion;
};
get itemsCount() {
return this.items.length;
}
}
export const TemperatureSensorStore = new Main();

View File

@@ -3,3 +3,6 @@ export interface TemperatureSensorItemProps {
name: string;
celsius: number;
}
export interface TemperatureSensorPollDataProps {
items: TemperatureSensorItemProps[];
}

View File

@@ -34,6 +34,8 @@ final class UpdaterActionVersion extends UpdaterConstants
])
->end();
}
$response->setStatus(StatusCode::$NO_CONTENT)->end();
$response
->setStatus(StatusCode::$NO_CONTENT)
->end();
}
}

View File

@@ -0,0 +1,5 @@
import type { FC, HTMLProps } from 'react';
import styles from './multi.module.scss';
export const UiMultiColContainer: FC<HTMLProps<HTMLDivElement>> = (props) => (
<div className={styles.main} {...props} />
);

View File

@@ -0,0 +1,5 @@
import type { FC, HTMLProps } from 'react';
import styles from './single.module.scss';
export const UiSingleColContainer: FC<HTMLProps<HTMLDivElement>> = (props) => (
<div className={styles.main} {...props} />
);

View File

@@ -1,13 +1,13 @@
import type { FC, HTMLProps, ReactNode } from 'react';
import styles from './description.module.scss';
import styles from './index.module.scss';
interface CardDescriptionProps extends HTMLProps<HTMLDivElement> {
interface UiDescriptionProps extends HTMLProps<HTMLDivElement> {
items: {
id: string;
text: ReactNode;
}[];
}
export const CardDescription: FC<CardDescriptionProps> = ({ items }) => (
export const UiDescription: FC<UiDescriptionProps> = ({ items }) => (
<ul className={styles.main}>
{items.map(({ id, text }) => (
<li className={styles.item} key={id}>

View File

@@ -0,0 +1,7 @@
import type { FC, HTMLAttributes } from 'react';
import styles from './index.module.scss';
export const UiError: FC<HTMLAttributes<HTMLDivElement>> = ({ children }) => (
<div className={styles.main} role="alert">
{children}
</div>
);

View File

@@ -1,11 +1,11 @@
import type { FC, HTMLProps, ReactNode } from 'react';
import styles from './ruby.module.scss';
export interface CardRubyProps extends HTMLProps<HTMLElement> {
import styles from './index.module.scss';
export interface UiRubyProps extends HTMLProps<HTMLElement> {
isResult?: boolean;
ruby: ReactNode;
rt: string;
}
export const CardRuby: FC<CardRubyProps> = ({
export const UiRuby: FC<UiRubyProps> = ({
ruby,
rt,
isResult = false,