mirror of
https://github.com/kmvan/x-prober.git
synced 2026-04-21 16:59:02 +08:00
completed modules
This commit is contained in:
@@ -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 /> */}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
.main {
|
||||
&::before {
|
||||
content: "👆 ";
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
@@ -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} />
|
||||
);
|
||||
@@ -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} />;
|
||||
@@ -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();
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { FC } from 'react';
|
||||
export interface CardProps {
|
||||
id: string;
|
||||
title: string;
|
||||
enabled?: boolean;
|
||||
priority: number;
|
||||
component: FC;
|
||||
nav: FC;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
});
|
||||
@@ -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}>
|
||||
34
src/Components/Module/components/index.tsx
Normal file
34
src/Components/Module/components/index.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
51
src/Components/Module/components/item.module.scss
Normal file
51
src/Components/Module/components/item.module.scss
Normal 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);
|
||||
}
|
||||
28
src/Components/Module/components/item.tsx
Normal file
28
src/Components/Module/components/item.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
29
src/Components/Module/components/preset.ts
Normal file
29
src/Components/Module/components/preset.ts
Normal 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,
|
||||
],
|
||||
};
|
||||
27
src/Components/Module/components/priority.ts
Normal file
27
src/Components/Module/components/priority.ts
Normal 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,
|
||||
];
|
||||
27
src/Components/Module/components/storage.ts
Normal file
27
src/Components/Module/components/storage.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
84
src/Components/Module/components/store.ts
Normal file
84
src/Components/Module/components/store.ts
Normal 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();
|
||||
11
src/Components/Module/components/typings.ts
Normal file
11
src/Components/Module/components/typings.ts
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
21
src/Components/ServerBenchmark/ServerBenchmarkPoll.php
Normal file
21
src/Components/ServerBenchmark/ServerBenchmarkPoll.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace InnStudio\Prober\Components\TemperatureSensor;
|
||||
|
||||
class TemperatureSensorConstants
|
||||
{
|
||||
public $ID = 'temperatureSensor';
|
||||
}
|
||||
@@ -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, [
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -3,3 +3,6 @@ export interface TemperatureSensorItemProps {
|
||||
name: string;
|
||||
celsius: number;
|
||||
}
|
||||
export interface TemperatureSensorPollDataProps {
|
||||
items: TemperatureSensorItemProps[];
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ final class UpdaterActionVersion extends UpdaterConstants
|
||||
])
|
||||
->end();
|
||||
}
|
||||
$response->setStatus(StatusCode::$NO_CONTENT)->end();
|
||||
$response
|
||||
->setStatus(StatusCode::$NO_CONTENT)
|
||||
->end();
|
||||
}
|
||||
}
|
||||
|
||||
5
src/Components/ui/col/multi-container.tsx
Normal file
5
src/Components/ui/col/multi-container.tsx
Normal 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} />
|
||||
);
|
||||
5
src/Components/ui/col/single-container.tsx
Normal file
5
src/Components/ui/col/single-container.tsx
Normal 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} />
|
||||
);
|
||||
@@ -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}>
|
||||
7
src/Components/ui/error/index.tsx
Normal file
7
src/Components/ui/error/index.tsx
Normal 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>
|
||||
);
|
||||
@@ -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,
|
||||
Reference in New Issue
Block a user