├── locales ├── zh.po ├── ja.mo ├── zh_CN.mo ├── zh_HK.mo └── zh_TW.mo ├── dev ├── main.tsx └── api.php ├── src ├── Components │ ├── Fetch │ │ ├── components │ │ │ └── index.tsx │ │ └── server-fetch.ts │ ├── scss.d.ts │ ├── MyInfo │ │ ├── components │ │ │ ├── index.module.scss │ │ │ ├── constants.ts │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ └── store.ts │ │ ├── MyInfoConstants.php │ │ └── MyInfoPoll.php │ ├── Ping │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── nav.tsx │ │ │ ├── loader.ts │ │ │ ├── index.tsx │ │ │ ├── style.module.scss │ │ │ └── store.ts │ │ ├── PingConstants.php │ │ ├── typings.ts │ │ ├── PingPoll.php │ │ └── PingAction.php │ ├── BrowserBenchmark │ │ ├── components │ │ │ ├── marks-meter.module.scss │ │ │ ├── constants.ts │ │ │ ├── index.module.scss │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── marks-meter.tsx │ │ │ ├── store.ts │ │ │ ├── index.tsx │ │ │ └── browsers-item.module.scss │ │ ├── BrowserBenchmarkConstants.php │ │ └── BrowserBenchmarkPoll.php │ ├── Nodes │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── cpu.module.scss │ │ │ ├── network.module.scss │ │ │ ├── node.module.scss │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── disk.module.scss │ │ │ ├── nav.tsx │ │ │ ├── ram.tsx │ │ │ ├── swap.tsx │ │ │ ├── index.tsx │ │ │ ├── usage.module.scss │ │ │ ├── index.module.scss │ │ │ ├── usage.tsx │ │ │ ├── store.ts │ │ │ ├── disk.tsx │ │ │ ├── network.tsx │ │ │ └── cpu.tsx │ │ ├── NodesConstants.php │ │ ├── NodesPoll.php │ │ └── NodesApi.php │ ├── ServerBenchmark │ │ ├── components │ │ │ ├── marks-meter.module.scss │ │ │ ├── constants.ts │ │ │ ├── index.module.scss │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── marks-meter.tsx │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ └── server-item.module.scss │ │ ├── ServerBenchmarkConstants.php │ │ ├── ServerBenchmarkDelay.php │ │ ├── ServerBenchmarkPoll.php │ │ └── ServerBenchmarkPerformanceAction.php │ ├── PhpInfo │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── loader.ts │ │ │ ├── typings.ts │ │ │ ├── nav.tsx │ │ │ ├── store.ts │ │ │ └── php-version.tsx │ │ ├── PhpInfoConstants.php │ │ └── PhpInfoPoll.php │ ├── ui │ │ ├── col │ │ │ ├── single.module.scss │ │ │ ├── multi.module.scss │ │ │ ├── single-container.tsx │ │ │ └── multi-container.tsx │ │ ├── error │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ │ ├── pie-chart │ │ │ ├── typings.ts │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── search-link │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ │ ├── enable-status │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ │ ├── description │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ │ └── ruby │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ ├── Database │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── typings.ts │ │ │ ├── index.module.scss │ │ │ ├── nav.tsx │ │ │ ├── loader.ts │ │ │ ├── store.ts │ │ │ └── index.tsx │ │ ├── DatabaseConstants.php │ │ └── DatabasePoll.php │ ├── DiskUsage │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── index.module.scss │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── store.ts │ │ │ └── index.tsx │ │ ├── DiskUsageConstants.php │ │ └── DiskUsagePoll.php │ ├── ServerInfo │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── index.module.scss │ │ │ ├── item.module.scss │ │ │ ├── loader.ts │ │ │ ├── typings.ts │ │ │ ├── nav.tsx │ │ │ └── store.ts │ │ ├── ServerInfoConstants.php │ │ ├── ServerInfoPublicIpv4Action.php │ │ ├── ServerInfoPublicIpv6Action.php │ │ ├── ServerInfoLocationIpv4Action.php │ │ └── ServerInfoPoll.php │ ├── NetworkStats │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── index.module.scss │ │ │ ├── store.ts │ │ │ ├── item.tsx │ │ │ └── item.module.scss │ │ ├── NetworkStatsConstants.php │ │ └── NetworkStatsPoll.php │ ├── ServerStatus │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── index.module.scss │ │ │ ├── loader.ts │ │ │ ├── swap-usage.tsx │ │ │ ├── swap-cached.tsx │ │ │ ├── nav.tsx │ │ │ ├── typings.ts │ │ │ ├── mem-cached.tsx │ │ │ ├── mem-buffers.tsx │ │ │ ├── mem-real-usage.tsx │ │ │ ├── cpu-usage.tsx │ │ │ ├── index.tsx │ │ │ └── system-load.module.scss │ │ └── ServerStatusConstants.php │ ├── PhpExtensions │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── store.ts │ │ │ └── typings.ts │ │ └── PhpExtensionsConstants.php │ ├── TemperatureSensor │ │ ├── components │ │ │ ├── constants.ts │ │ │ ├── typings.ts │ │ │ ├── loader.ts │ │ │ ├── nav.tsx │ │ │ ├── store.ts │ │ │ └── index.tsx │ │ └── TemperatureSensorConstants.php │ ├── ColorScheme │ │ ├── components │ │ │ ├── comm.scss │ │ │ ├── index.module.scss │ │ │ ├── index.tsx │ │ │ ├── config-dark.scss │ │ │ └── config.scss │ │ ├── constants.ts │ │ └── stores │ │ │ ├── color-schemes.ts │ │ │ └── index.ts │ ├── Location │ │ ├── components │ │ │ ├── typings.ts │ │ │ └── index.tsx │ │ ├── LocationConstants.php │ │ └── LocationIpv4Action.php │ ├── Poll │ │ ├── PoolConstants.php │ │ ├── components │ │ │ ├── store.ts │ │ │ └── typings.ts │ │ └── PollAction.php │ ├── Config │ │ ├── ConfigConstants.php │ │ ├── typings.ts │ │ ├── store.ts │ │ └── ConfigPoll.php │ ├── Updater │ │ ├── UpdaterConstants.php │ │ ├── UpdaterActionVersion.php │ │ └── components │ │ │ └── store.ts │ ├── Module │ │ └── components │ │ │ ├── index.module.scss │ │ │ ├── typings.ts │ │ │ ├── group.tsx │ │ │ ├── storage.ts │ │ │ ├── item.tsx │ │ │ ├── group.module.scss │ │ │ ├── arrow.module.scss │ │ │ ├── index.tsx │ │ │ ├── arrow.tsx │ │ │ ├── preset.ts │ │ │ └── priority.ts │ ├── Bootstrap │ │ ├── BootstrapConstants.php │ │ ├── components │ │ │ ├── loading.tsx │ │ │ ├── typings.ts │ │ │ ├── store.ts │ │ │ ├── index.module.scss │ │ │ ├── loading.module.scss │ │ │ └── global.scss │ │ └── Bootstrap.php │ ├── UserConfig │ │ ├── UserConfigConstants.php │ │ ├── UserConfigPoll.php │ │ ├── store.ts │ │ ├── UserConfigApi.php │ │ └── typings.ts │ ├── Utils │ │ ├── components │ │ │ ├── get-element-offset-top.ts │ │ │ ├── use-previous.ts │ │ │ ├── portal.tsx │ │ │ ├── hex-to-rgb.ts │ │ │ ├── mdev.ts │ │ │ ├── template.ts │ │ │ ├── rgba-to-hex.ts │ │ │ ├── format-bytes.ts │ │ │ ├── version-compare.ts │ │ │ ├── use-portal.ts │ │ │ ├── gradient.ts │ │ │ └── use-ip.ts │ │ ├── UtilsTime.php │ │ ├── UtilsClientIp.php │ │ └── UtilsNetwork.php │ ├── PhpInfoDetail │ │ ├── PhpInfoDetailConstants.php │ │ └── PhpInfoDetailAction.php │ ├── WindowConfig │ │ ├── components │ │ │ ├── typings.ts │ │ │ └── index.ts │ │ └── WindowConfigApi.php │ ├── Header │ │ └── components │ │ │ ├── name.module.scss │ │ │ ├── index.tsx │ │ │ ├── link.tsx │ │ │ ├── index.module.scss │ │ │ ├── bar.tsx │ │ │ ├── bar.module.scss │ │ │ └── link.module.scss │ ├── Placeholder │ │ ├── index.tsx │ │ └── index.module.scss │ ├── Timezone │ │ └── Timezone.php │ ├── Nav │ │ └── components │ │ │ ├── item.tsx │ │ │ ├── store.ts │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ ├── Style │ │ ├── components │ │ │ ├── device.scss │ │ │ └── devices.ts │ │ └── StyleAction.php │ ├── Button │ │ └── components │ │ │ ├── typings.ts │ │ │ └── index.tsx │ ├── Language │ │ └── index.ts │ ├── Script │ │ └── ScriptAction.php │ ├── Footer │ │ ├── Footer.php │ │ └── components │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── Toast │ │ └── components │ │ │ ├── index.module.scss │ │ │ ├── store.ts │ │ │ └── index.tsx │ ├── Events │ │ └── EventsApi.php │ └── Action │ │ └── Action.php ├── .vscode │ └── settings.json ├── env.d.ts └── main.tsx ├── .env.development ├── screenshots └── preview.webp ├── php-dev.sh ├── .vscode └── settings.json ├── Make.php ├── .gitignore ├── .code-workspace ├── php-build.sh ├── .github └── ISSUE_TEMPLATE │ ├── ask-question.md │ ├── bug_report.md │ └── feature_request.md ├── biome.jsonc ├── xconfig.json ├── tools ├── rm-files.mjs ├── po-parser.mjs └── lang-builder.mjs ├── composer.json ├── extensions └── temperature-sensors │ └── raspberrypi.js ├── tsconfig.json ├── dev.vite.config.mjs ├── prod.vite.config.mjs └── compiler ├── StyleGeneration.php └── ScriptGeneration.php /locales/zh.po: -------------------------------------------------------------------------------- 1 | zh_CN.po -------------------------------------------------------------------------------- /dev/main.tsx: -------------------------------------------------------------------------------- 1 | import '../src/main.tsx' 2 | -------------------------------------------------------------------------------- /src/Components/Fetch/components/index.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/scss.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss'; 2 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | VITE_DEV=true 2 | VITE_PORT=5173 3 | VITE_PHP_PORT=8000 -------------------------------------------------------------------------------- /locales/ja.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmvan/x-prober/HEAD/locales/ja.mo -------------------------------------------------------------------------------- /locales/zh_CN.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmvan/x-prober/HEAD/locales/zh_CN.mo -------------------------------------------------------------------------------- /locales/zh_HK.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmvan/x-prober/HEAD/locales/zh_HK.mo -------------------------------------------------------------------------------- /locales/zh_TW.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmvan/x-prober/HEAD/locales/zh_TW.mo -------------------------------------------------------------------------------- /src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "php-cs-fixer.config": "../.php-cs-fixer54" 3 | } -------------------------------------------------------------------------------- /src/Components/MyInfo/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | } 4 | -------------------------------------------------------------------------------- /screenshots/preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmvan/x-prober/HEAD/screenshots/preview.webp -------------------------------------------------------------------------------- /src/Components/Ping/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const PingConstants = { 2 | id: 'ping', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/marks-meter.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | } 4 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const NodesConstants = { 2 | id: 'nodes', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/marks-meter.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | } 4 | -------------------------------------------------------------------------------- /src/Components/MyInfo/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const MyInfoConstants = { 2 | id: 'myInfo', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/PhpInfo/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const PhpInfoConstants = { 2 | id: 'phpInfo', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/ui/col/single.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | gap: var(--x-gutter-sm); 4 | } 5 | -------------------------------------------------------------------------------- /php-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | php ./Make.php dev 4 | PHP_CLI_SERVER_WORKERS=8 php -S localhost:8000 -t ./dev -------------------------------------------------------------------------------- /src/Components/Database/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const DatabaseConstants = { 2 | id: 'database', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/DiskUsage/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const DiskUsageConstants = { 2 | id: 'diskUsage', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/Database/components/typings.ts: -------------------------------------------------------------------------------- 1 | export type DatabasePollDataProps = Record; 2 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/cpu.module.scss: -------------------------------------------------------------------------------- 1 | .sysLoad { 2 | display: flex; 3 | gap: var(--x-gutter-sm); 4 | } 5 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/network.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | gap: var(--x-gutter-sm); 4 | } 5 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const ServerInfoConstants = { 2 | id: 'serverInfo', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const NetworkStatsConstants = { 2 | id: 'networkStats', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const ServerStatusConstants = { 2 | id: 'serverStatus', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/PhpExtensions/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const PhpExtensionsConstants = { 2 | id: 'phpExtensions', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const ServerBenchmarkConstants = { 2 | id: 'serverBenchmark', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const BrowserBenchmarkConstants = { 2 | id: 'browserBenchmark', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Components/Database/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-wrap: wrap; 4 | gap: var(--x-gutter); 5 | } 6 | -------------------------------------------------------------------------------- /src/Components/TemperatureSensor/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const TemperatureSensorConstants = { 2 | id: 'temperatureSensor', 3 | }; 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "php-cs-fixer.config": ".php-cs-fixer54", 3 | "php-cs-fixer.allowRisky": true, 4 | "php.suggest.basic": false 5 | } 6 | -------------------------------------------------------------------------------- /src/Components/ColorScheme/components/comm.scss: -------------------------------------------------------------------------------- 1 | @mixin comm { 2 | :root { 3 | --x-gutter: 1rem; 4 | --x-gutter-sm: 0.5rem; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMetaEnv { 2 | MODE?: 'development' | 'production'; 3 | } 4 | interface ImportMeta { 5 | env: ImportMetaEnv; 6 | } 7 | -------------------------------------------------------------------------------- /src/Components/Location/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface LocationProps { 2 | continent: string; 3 | country: string; 4 | city: string; 5 | } 6 | -------------------------------------------------------------------------------- /Make.php: -------------------------------------------------------------------------------- 1 | 2 | Math.round(e.getBoundingClientRect().top + window.pageYOffset); 3 | -------------------------------------------------------------------------------- /src/Components/Bootstrap/components/loading.tsx: -------------------------------------------------------------------------------- 1 | import styles from './loading.module.scss'; 2 | export const BootstrapLoading = () => { 3 | return
Loading...
; 4 | }; 5 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/NetworkStatsConstants.php: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/node.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | gap: var(--x-gutter-sm); 4 | } 5 | .name { 6 | text-align: center; 7 | } 8 | .loading { 9 | display: grid; 10 | place-content: center center; 11 | height: 10rem; 12 | } 13 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | gap: var(--x-gutter-sm); 4 | } 5 | .modules { 6 | display: grid; 7 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 8 | gap: var(--x-gutter); 9 | } 10 | -------------------------------------------------------------------------------- /src/Components/TemperatureSensor/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface TemperatureSensorItemProps { 2 | id: string; 3 | name: string; 4 | celsius: number; 5 | } 6 | export interface TemperatureSensorPollDataProps { 7 | items: TemperatureSensorItemProps[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/Components/ui/col/single-container.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, HTMLProps } from 'react'; 2 | import styles from './single.module.scss'; 3 | export const UiSingleColContainer: FC> = (props) => ( 4 |
5 | ); 6 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface NetworkStatsItemProps { 2 | id: string; 3 | rx: number; 4 | tx: number; 5 | } 6 | export interface NetworkStatsPollDataProps { 7 | networks: NetworkStatsItemProps[]; 8 | timestamp: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/Components/Placeholder/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import styles from './index.module.scss'; 3 | export const Placeholder: FC<{ height?: number }> = ({ height = 5 }) => { 4 | return
; 5 | }; 6 | -------------------------------------------------------------------------------- /src/Components/Utils/components/use-previous.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | export function usePrevious(value: T): T | null { 3 | const ref = useRef(null); 4 | useEffect(() => { 5 | ref.current = value; 6 | }, [value]); 7 | return ref.current; 8 | } 9 | -------------------------------------------------------------------------------- /src/Components/ui/error/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, HTMLAttributes } from 'react'; 2 | import styles from './index.module.scss'; 3 | export const UiError: FC> = ({ children }) => ( 4 |
5 | {children} 6 |
7 | ); 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import { Bootstrap } from './Components/Bootstrap/components/index.tsx'; 3 | 4 | document.addEventListener('DOMContentLoaded', () => { 5 | document.body.innerHTML = ''; 6 | createRoot(document.body).render(); 7 | }); 8 | -------------------------------------------------------------------------------- /src/Components/Header/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import styles from './index.module.scss'; 3 | import { HeaderName } from './name.tsx'; 4 | export const Header: FC = () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /src/Components/ui/pie-chart/typings.ts: -------------------------------------------------------------------------------- 1 | export type PieChartStatusKey = 'Low' | 'Medium' | 'High'; 2 | export type PieChartStatusValue = 'low' | 'medium' | 'high'; 3 | export const PieChartStatus = { 4 | Low: 'low', 5 | Medium: 'medium', 6 | High: 'high', 7 | } satisfies Record; 8 | -------------------------------------------------------------------------------- /src/Components/Timezone/Timezone.php: -------------------------------------------------------------------------------- 1 | UserConfigApi::get(), 11 | ]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Components/Bootstrap/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface BootstrapPollDataProps { 2 | isDev: boolean; 3 | version: string; 4 | appName: string; 5 | appUrl: string; 6 | appConfigUrls: string[]; 7 | appConfigUrlDev: string; 8 | authorUrl: string; 9 | authorName: string; 10 | authorization: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/ServerBenchmarkDelay.php: -------------------------------------------------------------------------------- 1 | = ({ id, title }) => { 7 | return ( 8 | 9 | {title} 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/Components/Style/components/device.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | $breakPoints: ( 3 | xs: "320px", 4 | sm: "375px", 5 | md: "425px", 6 | lg: "768px", 7 | xl: "1024px", 8 | xxl: "1440px", 9 | 4k: "2560px" 10 | ); 11 | @mixin device($id) { 12 | @media (min-width: map.get($breakPoints, $id)) { 13 | @content; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Components/Ping/components/nav.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { NavItem } from '@/Components/Nav/components/item.tsx'; 4 | import { PingConstants } from './constants.ts';export const PingNav: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/Components/Utils/components/portal.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import { usePortal } from '@/Components/Utils/components/use-portal'; 4 | export const Portal: FC<{ children: ReactNode }> = ({ children }) => { 5 | const target = usePortal(); 6 | return createPortal(children, target); 7 | }; 8 | -------------------------------------------------------------------------------- /src/Components/Ping/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { PingConstants } from './constants.ts'; 3 | import { Ping as content } from './index.tsx'; 4 | import { PingNav as nav } from './nav.tsx';export const PingLoader: ModuleProps = { 5 | id: PingConstants.id, 6 | content, 7 | nav, 8 | }; 9 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | display: block; 3 | } 4 | .serversLoading { 5 | display: grid; 6 | justify-content: center; 7 | align-items: center; 8 | height: 5rem; 9 | } 10 | .servers { 11 | display: grid; 12 | grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr)); 13 | gap: var(--x-gutter-sm); 14 | } 15 | -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "linter": { 4 | "rules": { 5 | "complexity": { 6 | "noExcessiveCognitiveComplexity": "off" 7 | }, 8 | "security": { 9 | "noDangerouslySetInnerHtml": "off" 10 | } 11 | } 12 | }, 13 | "extends": [ 14 | "ultracite" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | display: block; 3 | } 4 | .browsersLoading { 5 | display: grid; 6 | justify-content: center; 7 | align-items: center; 8 | height: 5rem; 9 | } 10 | .browsers { 11 | display: grid; 12 | grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr)); 13 | gap: var(--x-gutter-sm); 14 | } 15 | -------------------------------------------------------------------------------- /src/Components/Database/components/nav.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { NavItem } from '@/Components/Nav/components/item.tsx'; 4 | import { DatabaseConstants } from './constants.ts';export const DatabaseNav: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/Components/Utils/components/hex-to-rgb.ts: -------------------------------------------------------------------------------- 1 | export const hexToRgb = (hex: string): number[] => { 2 | const newHex = hex.replace('#', ''); 3 | const arrBuff = new ArrayBuffer(4); 4 | const vw = new DataView(arrBuff); 5 | vw.setUint32(0, Number.parseInt(newHex, 16), false); 6 | const arrByte = new Uint8Array(arrBuff); 7 | return [arrByte[1], arrByte[2], arrByte[3]]; 8 | }; 9 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface BrowserBenchmarkMarksProps { 2 | js: number; 3 | dom: number; 4 | canvas: number; 5 | } 6 | export interface BrowserBenchmarkProps { 7 | id: string; 8 | name: string; 9 | version: string; 10 | ua: string; 11 | date: string; 12 | total: number; 13 | detail: BrowserBenchmarkMarksProps; 14 | } 15 | -------------------------------------------------------------------------------- /src/Components/MyInfo/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { MyInfoConstants } from './constants.ts'; 3 | import { MyInfo as content } from './index.tsx'; 4 | import { MyInfoNav as nav } from './nav'; 5 | export const MyInfoLoader: ModuleProps = { 6 | id: MyInfoConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { NodesConstants } from './constants.ts'; 3 | import { Nodes as content } from './index.tsx'; 4 | import { NodesNav as nav } from './nav.tsx'; 5 | export const NodesLoader: ModuleProps = { 6 | id: NodesConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/DiskUsage/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { DiskUsage as content } from '.'; 3 | import { DiskUsageConstants } from './constants'; 4 | import { DiskUsageNav as nav } from './nav'; 5 | export const DiskUsageLoader: ModuleProps = { 6 | id: DiskUsageConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/Button/components/typings.ts: -------------------------------------------------------------------------------- 1 | export type ButtonStatusKey = 'Error' | 'Loading' | 'Warning' | 'Pointer'; 2 | export type ButtonStatusValue = 'error' | 'loading' | 'warning' | 'pointer'; 3 | export const ButtonStatus = { 4 | Error: 'error', 5 | Loading: 'loading', 6 | Warning: 'warning', 7 | Pointer: 'pointer', 8 | } as const satisfies Record; 9 | -------------------------------------------------------------------------------- /src/Components/PhpInfo/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { PhpInfoConstants } from './constants.ts'; 3 | import { PhpInfo as content } from './index.tsx'; 4 | import { PhpInfoNav as nav } from './nav.tsx'; 5 | export const PhpInfoLoader: ModuleProps = { 6 | id: PhpInfoConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/Database/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { DatabaseConstants } from './constants.ts'; 3 | import { Database as content } from './index.tsx'; 4 | import { DatabaseNav as nav } from './nav.tsx'; 5 | export const DatabaseLoader: ModuleProps = { 6 | id: DatabaseConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { ServerInfoConstants } from './constants.ts'; 3 | import { ServerInfo as content } from './index.tsx'; 4 | import { ServerInfoNav as nav } from './nav.tsx';export const ServerInfoLoader: ModuleProps = { 5 | id: ServerInfoConstants.id, 6 | content, 7 | nav, 8 | }; 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: kmvan 7 | 8 | --- 9 | 10 | - OS: 11 | - PHP version: 12 | - Browser: 13 | 14 | ## Expected behavior 15 | 16 | 17 | ## Actual behavior 18 | 19 | 20 | ## Other description 21 | 22 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/typings.ts: -------------------------------------------------------------------------------- 1 | export interface ServerBenchmarkMarksProps { 2 | cpu: number; 3 | read: number; 4 | write: number; 5 | } 6 | export interface ServerBenchmarkProps { 7 | id: string; 8 | name: string; 9 | url: string; 10 | date: string; 11 | probeUrl: string; 12 | binUrl: string; 13 | total: number; 14 | detail: ServerBenchmarkMarksProps; 15 | } 16 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/disk.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | gap: var(--x-gutter-sm); 4 | container-type: inline-size; 5 | max-height: calc(50px * 2 + var(--x-gutter-sm)); 6 | overflow-y: auto; 7 | overscroll-behavior: contain; 8 | scroll-snap-type: y mandatory; 9 | scrollbar-color: hsl(0 0% 50% / 0.5) transparent; 10 | } 11 | .item { 12 | scroll-snap-align: start; 13 | } 14 | -------------------------------------------------------------------------------- /src/Components/WindowConfig/components/index.ts: -------------------------------------------------------------------------------- 1 | import type { WindowConfigProps, WindowProps } from './typings.ts'; 2 | export const WindowConfig = { 3 | IS_DEV: Boolean( 4 | (window as unknown as WindowProps)?.GLOBAL_CONFIG?.IS_DEV ?? false 5 | ), 6 | AUTHORIZATION: String( 7 | (window as unknown as WindowProps)?.GLOBAL_CONFIG?.AUTHORIZATION ?? '' 8 | ), 9 | } as const satisfies WindowConfigProps; 10 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { NetworkStatsConstants } from './constants.ts'; 3 | import { NetworkStats as content } from './index.tsx'; 4 | import { NetworkStatsNav as nav } from './nav.tsx'; 5 | export const NetworkStatsLoader: ModuleProps = { 6 | id: NetworkStatsConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { ServerStatusConstants } from './constants.ts'; 3 | import { ServerStatus as content } from './index.tsx'; 4 | import { ServerStatusNav as nav } from './nav.tsx'; 5 | export const ServerStatusLoader: ModuleProps = { 6 | id: ServerStatusConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/PhpExtensions/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { PhpExtensionsConstants } from './constants.ts'; 3 | import { PhpExtensions as content } from './index.tsx'; 4 | import { PhpExtensionsNav as nav } from './nav.tsx'; 5 | export const PhpExtensionsLoader: ModuleProps = { 6 | id: PhpExtensionsConstants.id, 7 | content, 8 | nav, 9 | }; 10 | -------------------------------------------------------------------------------- /src/Components/ui/search-link/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import styles from './index.module.scss';export const SearchLink: FC<{ 3 | keyword: string; 4 | }> = ({ keyword }) => ( 5 | 11 | {keyword} 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/Components/TemperatureSensor/components/loader.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleProps } from '@/Components/Module/components/typings.ts'; 2 | import { TemperatureSensorConstants } from './constants.ts'; 3 | import { TemperatureSensor as content } from './index.tsx'; 4 | import { TemperatureSensorNav as nav } from './nav.tsx';export const TemperatureSensorLoader: ModuleProps = { 5 | id: TemperatureSensorConstants.id, 6 | content, 7 | nav, 8 | }; 9 | -------------------------------------------------------------------------------- /src/Components/Header/components/link.tsx: -------------------------------------------------------------------------------- 1 | import type { AnchorHTMLAttributes, ButtonHTMLAttributes, FC } from 'react'; 2 | import styles from './link.module.scss'; 3 | export const HeaderLink: FC> = ( 4 | props 5 | ) => ; 6 | export const HeaderButton: FC> = ( 7 | props 8 | ) => 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /src/Components/Module/components/storage.ts: -------------------------------------------------------------------------------- 1 | import type { StoragePriorityItemProps } from './store.ts';const STORAGE_KEY = 'module-priority'; 2 | export const ModuleStorage = { 3 | getItems(): Record { 4 | const items = localStorage.getItem(STORAGE_KEY); 5 | if (!items) { 6 | return {}; 7 | } 8 | try { 9 | return JSON.parse(items) as Record; 10 | } catch { 11 | return {}; 12 | } 13 | }, 14 | setItems(items: Record) { 15 | localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); 16 | }, 17 | getPriority(id: string): number { 18 | return this.getItems()[id] || 0; 19 | }, 20 | setPriority({ id, priority }: StoragePriorityItemProps) { 21 | const items = this.getItems(); 22 | items[id] = priority; 23 | this.setItems(items); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/mem-cached.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components/index.tsx'; 5 | import { ServerStatusStore } from './store.ts'; 6 | export const MemCached: FC = observer(() => { 7 | const { max, value } = ServerStatusStore.memCached; 8 | return ( 9 | 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Components/TemperatureSensor/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { isDeepEqual } from '@/Components/Utils/components/is-deep-equal/index.ts'; 3 | import type { TemperatureSensorPollDataProps } from './typings.ts';configure({ 4 | enforceActions: 'observed', 5 | }); 6 | class Main { 7 | pollData: TemperatureSensorPollDataProps | null = null; 8 | latestPhpVersion = ''; 9 | constructor() { 10 | makeAutoObservable(this); 11 | } 12 | setPollData = (pollData: TemperatureSensorPollDataProps | null) => { 13 | if (isDeepEqual(pollData, this.pollData)) { 14 | return; 15 | } 16 | this.pollData = pollData; 17 | }; 18 | setLatestPhpVersion = (latestPhpVersion: string) => { 19 | this.latestPhpVersion = latestPhpVersion; 20 | }; 21 | } 22 | export const TemperatureSensorStore = new Main(); 23 | -------------------------------------------------------------------------------- /src/Components/ui/search-link/index.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-search-fg: var(--x-fg); 3 | --x-search-bg: hsl(0 0% 0% / 0.1); 4 | --x-search-bg-hover: hsl(0 0% 0% / 0.15); 5 | --x-search-bg-active: hsl(0 0% 0% / 0.2); 6 | @media (prefers-color-scheme: dark) { 7 | --x-search-fg: var(--x-fg); 8 | --x-search-bg: hsl(0 0% 100% / 0.1); 9 | --x-search-bg-hover: hsl(0 0% 100% / 0.15); 10 | --x-search-bg-active: hsl(0 0% 100% / 0.2); 11 | } 12 | } 13 | .main { 14 | border-radius: var(--x-radius); 15 | background: var(--x-search-bg); 16 | padding: calc(var(--x-gutter-sm) * 0.5) var(--x-gutter-sm); 17 | color: var(--x-search-fg); 18 | font-family: monospace; 19 | &:hover { 20 | background: var(--x-search-bg-hover); 21 | text-decoration: none; 22 | } 23 | &:active { 24 | background: var(--x-search-bg-active); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Components/Header/components/bar.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-bar-fg: hsl(0 0% 0% / 0.5); 3 | --x-bar-bg-hover: hsl(0 0% 0% / 0.1); 4 | --x-bar-bg-active: hsl(0 0% 0% / 0.15); 5 | @media (prefers-color-scheme: dark) { 6 | --x-bar-fg: hsl(0 0% 100% / 0.5); 7 | --x-bar-bg-hover: hsl(0 0% 100% / 0.1); 8 | --x-bar-bg-active: hsl(0 0% 100% / 0.15); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | place-content: center; 14 | gap: 0.2rem; 15 | cursor: pointer; 16 | border: none; 17 | background: none; 18 | padding: 0; 19 | width: 3rem; 20 | height: 100%; 21 | &:hover { 22 | background: var(--x-bar-bg-hover); 23 | } 24 | &:active { 25 | background: var(--x-bar-bg-active); 26 | } 27 | } 28 | .line { 29 | border-radius: var(--x-border-radius); 30 | background: var(--x-bar-fg); 31 | width: 1rem; 32 | height: 2px; 33 | } 34 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/ram.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import type { ServerStatusUsageProps } from '@/Components/ServerStatus/components/typings.ts'; 4 | import { formatBytes } from '@/Components/Utils/components/format-bytes.ts'; 5 | import { NodesUsage, NodesUsageLabel, NodesUsageOverview } from './usage.tsx'; 6 | export const NodesRam: FC<{ data: ServerStatusUsageProps }> = memo( 7 | ({ data }) => { 8 | const { value, max } = data; 9 | const percent = max ? Math.round((value / max) * 100) : 0; 10 | return ( 11 | 12 | {`🐏 ${gettext('Ram')}`} 13 | {`${formatBytes(value)} / ${formatBytes(max)}`} 14 | 15 | ); 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/swap.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import type { ServerStatusUsageProps } from '@/Components/ServerStatus/components/typings.ts'; 4 | import { formatBytes } from '@/Components/Utils/components/format-bytes.ts'; 5 | import { NodesUsage, NodesUsageLabel, NodesUsageOverview } from './usage.tsx'; 6 | export const NodesSwap: FC<{ data: ServerStatusUsageProps }> = memo( 7 | ({ data }) => { 8 | const { value, max } = data; 9 | const percent = max ? Math.round((value / max) * 100) : 0; 10 | return ( 11 | 12 | {`🐏 ${gettext('Swap')}`} 13 | {`${formatBytes(value)} / ${formatBytes(max)}`} 14 | 15 | ); 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /src/Components/Nodes/NodesApi.php: -------------------------------------------------------------------------------- 1 | $item[0], 25 | 'url' => $item[1], 26 | ]; 27 | }, $items) 28 | ) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Components/ui/enable-status/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: inline-flex; 3 | border-radius: var(--x-radius); 4 | align-items: center; 5 | justify-content: center; 6 | font-family: 'Arial Black', sans-serif; 7 | font-weight: bolder; 8 | min-width: 2em; 9 | padding: 0 0.5rem; 10 | white-space: nowrap; 11 | cursor: pointer; 12 | text-shadow: 0 1px 1px #000; 13 | &:active { 14 | transform: scale3d(0.95, 0.95, 1); 15 | } 16 | &[data-ok] { 17 | background: var(--x-status-ok-bg); 18 | color: var(--x-status-ok-fg); 19 | } 20 | &[data-error] { 21 | background: var(--x-status-error-bg); 22 | color: var(--x-status-error-fg); 23 | } 24 | 25 | &[data-ok][data-icon] { 26 | &::before { 27 | content: '✓'; 28 | } 29 | } 30 | &[data-error][data-icon] { 31 | &::before { 32 | content: '×'; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Components/Module/components/item.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from 'react'; 2 | import { ModuleArrow } from '@/Components/Module/components/arrow.tsx'; 3 | import styles from './item.module.scss';const ModuleItemTitle: FC<{ 4 | id: string; 5 | title: string; 6 | }> = ({ id, title }) => { 7 | return ( 8 |

9 | 10 | {title} 11 | 12 |

13 | ); 14 | }; 15 | export const ModuleItem: FC<{ 16 | id: string; 17 | title: string; 18 | children: ReactNode; 19 | }> = ({ id, title, children, ...props }) => { 20 | return ( 21 |
22 | 23 |
{children}
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { isDeepEqual } from '@/Components/Utils/components/is-deep-equal/index.ts'; 3 | import type { ServerInfoPollDataProps } from './typings.ts';configure({ 4 | enforceActions: 'observed', 5 | }); 6 | class Main { 7 | pollData: ServerInfoPollDataProps | null = null; 8 | publicIpv4 = ''; 9 | publicIpv6 = ''; 10 | constructor() { 11 | makeAutoObservable(this); 12 | } 13 | setPollData = (pollData: ServerInfoPollDataProps | null) => { 14 | if (isDeepEqual(pollData, this.pollData)) { 15 | return; 16 | } 17 | this.pollData = pollData; 18 | }; 19 | setPublicIpv4 = (ipv4: string) => { 20 | this.publicIpv4 = ipv4; 21 | }; 22 | setPublicIpv6 = (ipv6: string) => { 23 | this.publicIpv6 = ipv6; 24 | }; 25 | } 26 | export const ServerInfoStore = new Main(); 27 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 5 | import { NodesConstants } from './constants.ts'; 6 | import styles from './index.module.scss'; 7 | import { Node } from './node.tsx'; 8 | import { NodesStore } from './store'; 9 | export const Nodes: FC = observer(() => { 10 | const { pollData } = NodesStore; 11 | const nodeIds = pollData?.nodesIds ?? []; 12 | if (!nodeIds.length) { 13 | return null; 14 | } 15 | return ( 16 | 17 |
18 | {nodeIds.map((id) => ( 19 | 20 | ))} 21 |
22 |
23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/mem-buffers.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components/index.tsx'; 5 | import { ServerStatusStore } from './store.ts'; 6 | export const MemBuffers: FC = observer(() => { 7 | const { max, value } = ServerStatusStore.memBuffers; 8 | return ( 9 | 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/mem-real-usage.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components/index.tsx'; 5 | import { ServerStatusStore } from './store.ts'; 6 | export const MemRealUsage: FC = observer(() => { 7 | const { max, value } = ServerStatusStore.memRealUsage; 8 | return ( 9 | 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Components/Toast/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC, MouseEvent } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Portal } from '@/Components/Utils/components/portal.tsx'; 5 | import styles from './index.module.scss'; 6 | import { ToastStore } from './store.ts'; 7 | export const Toast: FC = observer(() => { 8 | const { isOpen, msg, close } = ToastStore; 9 | const handleClose = (e: MouseEvent) => { 10 | e.preventDefault(); 11 | e.stopPropagation(); 12 | close(); 13 | }; 14 | if (!isOpen) { 15 | return null; 16 | } 17 | return ( 18 | 19 | 27 | 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/Components/ColorScheme/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { ColorSchemeConstants } from '../constants'; 3 | import { colorSchemes } from './color-schemes';configure({ 4 | enforceActions: 'observed', 5 | }); 6 | const { storageId } = ColorSchemeConstants; 7 | class Main { 8 | schemeId: string = this.getStorageSchemeId(); 9 | constructor() { 10 | makeAutoObservable(this); 11 | } 12 | setSchemeId = (schemeId: string) => { 13 | this.schemeId = schemeId; 14 | this.setStorageSchemeId(schemeId); 15 | }; 16 | get scheme() { 17 | return colorSchemes?.[this.schemeId] ?? colorSchemes.default; 18 | } 19 | private getStorageSchemeId(): string { 20 | return localStorage.getItem(storageId) || 'default'; 21 | } 22 | private setStorageSchemeId = (schemeId: string) => { 23 | localStorage.setItem(storageId, schemeId); 24 | }; 25 | } 26 | export const ColorSchemeStore = new Main(); 27 | -------------------------------------------------------------------------------- /src/Components/Ping/PingAction.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::NOT_IMPLEMENTED) 21 | ->end(); 22 | } 23 | $response 24 | ->setData([ 25 | 'id' => (string) microtime(true), 26 | 'time' => \defined('XPROBER_TIMER') ? microtime(true) - XPROBER_TIMER : 0, 27 | ]) 28 | ->end(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Components/ui/description/index.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-card-des-fg: var(--x-fg); 3 | --x-card-des-bg: hsl(0 0% 100% / 0.1); 4 | --x-card-des-accent: hsl(0 0% 0% / 0.5); 5 | @media (prefers-color-scheme: dark) { 6 | --x-card-des-fg: var(--x-fg); 7 | --x-card-des-bg: hsl(0 0% 100% / 0.1); 8 | --x-card-des-accent: hsl(209, 100%, 63%); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | border-radius: var(--x-radius); 14 | color: var(--x-card-des-fg); 15 | font-family: var(--x-text-font-family); 16 | // background: var(--x-card-des-bg); 17 | // padding: var(--x-gutter-sm) var(--x-gutter) var(--x-gutter-sm) 2em; 18 | list-style-type: none; 19 | } 20 | .item { 21 | display: flex; 22 | align-items: center; 23 | gap: var(--x-gutter-sm); 24 | &::before { 25 | border-radius: var(--x-radius); 26 | background: var(--x-card-des-accent); 27 | width: 2px; 28 | height: 50%; 29 | content: ""; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/cpu-usage.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components/index.tsx'; 5 | import { template } from '@/Components/Utils/components/template'; 6 | import { ServerStatusStore } from './store.ts'; 7 | export const CpuUsage: FC = observer(() => { 8 | const { cpuUsage } = ServerStatusStore; 9 | const { idle } = cpuUsage; 10 | return ( 11 | 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr)); 4 | gap: var(--x-gutter); 5 | } 6 | .item { 7 | display: grid; 8 | } 9 | .id { 10 | text-align: center; 11 | text-decoration: underline; 12 | } 13 | .idRow { 14 | display: grid; 15 | align-items: center; 16 | } 17 | .dataContainer { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | text-align: center; 22 | } 23 | .data { 24 | flex: 0 0 50%; 25 | &[data-rx] { 26 | color: var(--x-network-stats-rx-fg); 27 | } 28 | &[data-tx] { 29 | color: var(--x-network-stats-tx-fg); 30 | } 31 | } 32 | 33 | .rate { 34 | font-family: "Arial Black", sans-serif; 35 | &::before { 36 | margin-right: 0.5rem; 37 | } 38 | } 39 | .rateRx { 40 | &::before { 41 | content: "▼"; 42 | } 43 | } 44 | .rateTx { 45 | &::before { 46 | content: "▲"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/ServerInfoPublicIpv4Action.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::FORBIDDEN) 20 | ->end(); 21 | } 22 | (new RestResponse()) 23 | ->setData([ 24 | 'ip' => UtilsServerIp::getPublicIpV4(), 25 | ]) 26 | ->end(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/ServerInfoPublicIpv6Action.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::FORBIDDEN) 20 | ->end(); 21 | } 22 | (new RestResponse()) 23 | ->setData([ 24 | 'ip' => UtilsServerIp::getPublicIpV6(), 25 | ]) 26 | ->end(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/usage.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | grid-template-columns: 1fr auto; 4 | grid-template-areas: 5 | "x-nodes-usage-label x-nodes-usage-label" 6 | "x-nodes-usage-overview x-nodes-usage-percent" 7 | "x-nodes-usage-meter x-nodes-usage-meter"; 8 | column-gap: var(--x-gutter-sm); 9 | row-gap: 0; 10 | // justify-content: center; 11 | // align-items: center; 12 | gap: var(--x-gutter-sm); 13 | } 14 | .meter { 15 | display: flex; 16 | grid-area: x-nodes-usage-meter; 17 | height: var(--x-meter-height); 18 | } 19 | .label { 20 | // display: flex; 21 | grid-area: x-nodes-usage-label; 22 | // justify-content: center; 23 | // align-items: center; 24 | } 25 | .overview { 26 | display: flex; 27 | grid-area: x-nodes-usage-overview; 28 | gap: var(--x-gutter-sm); 29 | } 30 | .chart { 31 | display: none; 32 | // display: grid; 33 | grid-area: x-nodes-usage-chart; 34 | } 35 | .percent { 36 | grid-area: x-nodes-usage-percent; 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/Ping/components/style.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-ping-result-scrollbar-bg: hsl(0 0% 0% / 0.5); 3 | --x-ping-item-bg: hsl(0 0% 0% / 0.1); 4 | @media (prefers-color-scheme: dark) { 5 | --x-ping-result-scrollbar-bg: hsl(0 0% 100% / 0.5); 6 | --x-ping-item-bg: hsl(0 0% 100% / 0.1); 7 | } 8 | } 9 | .itemContainer { 10 | display: grid; 11 | grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr)); 12 | grid-auto-flow: row; 13 | flex-grow: 1; 14 | gap: 0.15em; 15 | border-radius: var(--x-radius); 16 | background: var(--x-ping-item-bg); 17 | padding: var(--x-gutter-sm) var(--x-gutter); 18 | height: 7rem; 19 | overflow-y: auto; 20 | scrollbar-color: var(--x-ping-result-scrollbar-bg) transparent; 21 | list-style-type: none; 22 | } 23 | .resultContainer { 24 | display: grid; 25 | flex-grow: 1; 26 | gap: var(--x-gutter-sm); 27 | } 28 | .result { 29 | display: flex; 30 | flex-wrap: wrap; 31 | justify-content: space-between; 32 | align-items: center; 33 | } 34 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 4 | import { UiDescription } from '@/Components/ui/description/index.tsx'; 5 | import { ServerBenchmarkConstants } from './constants.ts'; 6 | import { ServerBenchmarkServers } from './servers.tsx'; 7 | export const ServerBenchmark: FC = memo(() => { 8 | return ( 9 | 13 | 23 | 24 | 25 | ); 26 | }); 27 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import type { ServerBenchmarkProps } from './typings.ts'; 3 | 4 | configure({ 5 | enforceActions: 'observed', 6 | }); 7 | class Main { 8 | benchmarking = false; 9 | maxMarks = 0; 10 | servers: ServerBenchmarkProps[] = []; 11 | constructor() { 12 | makeAutoObservable(this); 13 | } 14 | setMaxMarks = (maxMarks: number) => { 15 | this.maxMarks = maxMarks; 16 | }; 17 | setServers = (servers: ServerBenchmarkProps[]) => { 18 | this.servers = servers; 19 | }; 20 | setServer = ( 21 | id: ServerBenchmarkProps['id'], 22 | server: ServerBenchmarkProps 23 | ) => { 24 | const i = this.servers.findIndex((n) => n.id === id); 25 | if (i === -1) { 26 | return; 27 | } 28 | this.servers[i] = server; 29 | }; 30 | setBenchmarking = (benchmarking: boolean) => { 31 | this.benchmarking = benchmarking; 32 | }; 33 | } 34 | export const ServerBenchmarkStore = new Main(); 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kmvan/prober", 3 | "description": "This is a probe program for PHP environment. It can show your server information and readable easily. And the most important thing is that it's like iphone iPhone X/XS/XS Max/XR/11/11 Pro/11 Pro Max !", 4 | "type": "project", 5 | "license": "GPL-2.0", 6 | "authors": [ 7 | { 8 | "name": "Km.Van", 9 | "email": "kmvan.com@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "prefer-stable": true, 14 | "archive": { 15 | "exclude": [ 16 | "/dist", 17 | "node_modules" 18 | ] 19 | }, 20 | "require": { 21 | "friendsofphp/php-cs-fixer": "^3.8" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "InnStudio\\Prober\\": "src/", 26 | "InnStudio\\Prober\\Components\\": "src/Components/", 27 | "InnStudio\\Prober\\Compiler\\": "compiler/" 28 | } 29 | }, 30 | "scripts": { 31 | "fix": [ 32 | "php-cs-fixer fix --config .php-cs-fixer53.php --verbose" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import type { BrowserBenchmarkProps } from './typings.ts'; 3 | 4 | configure({ 5 | enforceActions: 'observed', 6 | }); 7 | class Main { 8 | benchmarking = false; 9 | maxMarks = 0; 10 | browsers: BrowserBenchmarkProps[] = []; 11 | constructor() { 12 | makeAutoObservable(this); 13 | } 14 | setMaxMarks = (maxMarks: number) => { 15 | this.maxMarks = maxMarks; 16 | }; 17 | setBrowsers = (browsers: BrowserBenchmarkProps[]) => { 18 | this.browsers = browsers; 19 | }; 20 | setBrowser = ( 21 | id: BrowserBenchmarkProps['id'], 22 | item: BrowserBenchmarkProps 23 | ) => { 24 | const i = this.browsers.findIndex((n) => n.id === id); 25 | if (i === -1) { 26 | return; 27 | } 28 | this.browsers[i] = item; 29 | }; 30 | setBenchmarking = (benchmarking: boolean) => { 31 | this.benchmarking = benchmarking; 32 | }; 33 | } 34 | export const BrowserBenchmarkStore = new Main(); 35 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 4 | import { ServerStatusConstants } from './constants.ts'; 5 | import styles from './index.module.scss'; 6 | import { MemBuffers } from './mem-buffers'; 7 | import { MemCached } from './mem-cached'; 8 | import { MemRealUsage } from './mem-real-usage'; 9 | import { SwapCached } from './swap-cached'; 10 | import { SwapUsage } from './swap-usage'; 11 | import { SystemLoad } from './system-load'; 12 | export const ServerStatus: FC = () => ( 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | ); 26 | -------------------------------------------------------------------------------- /src/Components/Footer/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { PollStore } from '@/Components/Poll/components/store.ts'; 5 | import { template } from '@/Components/Utils/components/template'; 6 | import styles from './index.module.scss'; 7 | export const Footer: FC = observer(() => { 8 | const { pollData } = PollStore; 9 | if (!pollData?.config) { 10 | return null; 11 | } 12 | const { APP_NAME, APP_URL, AUTHOR_NAME, AUTHOR_URL } = pollData.config; 13 | return ( 14 |
${APP_NAME}`, 21 | authorName: `${AUTHOR_NAME}`, 22 | } 23 | ), 24 | }} 25 | /> 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/Components/Header/components/link.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-link-fg: hsl(0 0% 95% / 0.95); 3 | --x-link-bg: hsl(0 0% 15% / 0.95); 4 | --x-link-bg-hover: hsl(0 0% 20% / 0.95); 5 | --x-link-bg-active: hsl(0 0% 25% / 0.95); 6 | @media (prefers-color-scheme: dark) { 7 | --x-link-fg: hsl(0 0% 100% / 0.95); 8 | --x-link-bg: hsl(0 0% 10% / 0.95); 9 | --x-link-bg-hover: hsl(0 0% 15% / 0.95); 10 | --x-link-bg-active: hsl(0 0% 20% / 0.95); 11 | } 12 | } 13 | .main { 14 | display: flex; 15 | align-items: center; 16 | gap: var(--x-gutter-sm); 17 | cursor: pointer; 18 | border: none; 19 | border-radius: 10rem; 20 | background: var(--x-link-bg); 21 | padding: var(--x-gutter-sm) var(--x-gutter); 22 | color: var(--x-link-fg); 23 | text-decoration: none; 24 | &:hover { 25 | background: var(--x-link-bg-hover); 26 | color: var(--x-link-fg); 27 | text-decoration: none; 28 | } 29 | &:active { 30 | background: var(--x-link-bg-active); 31 | color: var(--x-link-fg); 32 | text-decoration: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extensions/temperature-sensors/raspberrypi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Raspberry PI Series (Linux) Temperature CPU 3 | * 4 | * @usage `node ./raspberrypi.js` 5 | */ 6 | 7 | //import sf and http modules 8 | const fs = require('fs') 9 | const http = require('http') 10 | let temp 11 | 12 | //read CPU temperature every second 13 | setInterval(() => { 14 | temp = fs.readFileSync('/sys/class/thermal/thermal_zone0/temp') 15 | }, 1000) 16 | 17 | //creat http server 18 | const server = http.createServer((request, response) => { 19 | //write response header 20 | response.writeHead(200, { 21 | //defining document type then charset "utf-8" 22 | 'Content-Type': 'application/json;charset="utf-8"', 23 | //CORS 24 | 'Access-Control-Allow-Origin': '*', 25 | }) 26 | const items = [ 27 | { 28 | id: 'cpu', 29 | name: 'CPU', 30 | celsius: temp / 1000, 31 | }, 32 | ] 33 | //transform object into string, and output with .end() method 34 | response.end(JSON.stringify(items)) 35 | }) 36 | //listen port and IP address 37 | server.listen(4096, '127.0.0.1') 38 | -------------------------------------------------------------------------------- /src/Components/Module/components/group.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-card-group-label-fg: var(--x-fg); 3 | --x-card-group-split-color: hsl(0 0% 0% / 0.1); 4 | --x-card-group-bg-hover: hsl(0 0% 0% / 0.05); 5 | @media (prefers-color-scheme: dark) { 6 | --x-card-group-label-fg: var(--x-fg); 7 | --x-card-group-split-color: hsl(0 0% 100% / 0.1); 8 | --x-card-group-bg-hover: hsl(0 0% 100% / 0.05); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | grid-template-columns: minmax(var(--min-width), var(--max-width)) 1fr; 14 | gap: var(--x-gutter-sm); 15 | border-radius: var(--x-radius); 16 | &:hover { 17 | background: var(--x-card-group-bg-hover); 18 | } 19 | } 20 | .label { 21 | // display: flex; 22 | // align-items: center; 23 | color: var(--x-card-group-label-fg); 24 | font-family: var(--x-text-font-family); 25 | text-align: right; 26 | word-break: normal; 27 | &::after { 28 | content: ":"; 29 | } 30 | } 31 | .content { 32 | display: flex; 33 | flex-wrap: wrap; 34 | align-items: flex-start; 35 | gap: var(--x-gutter-sm); 36 | } 37 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 4 | import { UiDescription } from '@/Components/ui/description/index.tsx'; 5 | import { BrowserBenchmarkBrowsers } from './browsers.tsx'; 6 | import { BrowserBenchmarkConstants } from './constants.ts'; 7 | import { BrowserBenchmarkMyBrowser } from './my-browser.tsx'; 8 | export const BrowserBenchmark: FC = memo(() => { 9 | return ( 10 | 14 | 24 | 25 | 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/Components/Placeholder/index.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-placeholder-bg: linear-gradient(to right, hsl(0 0% 0% / 0.1) 46%, hsl(0 0% 0% / 0.15) 50%, hsl(0 0% 0% / 0.1) 54%) 3 | 50% 50%; 4 | @media (prefers-color-scheme: dark) { 5 | --x-placeholder-bg: linear-gradient( 6 | to right, 7 | hsl(0 0% 100% / 0.1) 46%, 8 | hsl(0 0% 100% / 0.15) 50%, 9 | hsl(0 0% 100% / 0.1) 54% 10 | ) 11 | 50% 50%; 12 | } 13 | } 14 | @keyframes animation { 15 | 0% { 16 | transform: translate3d(-30%, 0, 0); 17 | } 18 | 19 | 100% { 20 | transform: translate3d(30%, 0, 0); 21 | } 22 | } 23 | .main { 24 | position: relative; 25 | border-radius: var(--x-radius); 26 | overflow: hidden; 27 | &::before { 28 | position: absolute; 29 | top: 0; 30 | right: 0; 31 | bottom: 0; 32 | left: 50%; 33 | z-index: 1; 34 | animation: animation 1s linear infinite; 35 | margin-left: -250%; 36 | background: var(--x-placeholder-bg); 37 | width: 500%; 38 | pointer-events: none; 39 | content: " "; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Components/Fetch/server-fetch.ts: -------------------------------------------------------------------------------- 1 | import { WindowConfig } from '../WindowConfig/components/index.ts'; 2 | 3 | interface ServerFetchProps { 4 | data: T | null; 5 | status: number; 6 | } 7 | const isDev = import.meta.env?.MODE === 'development'; 8 | export const serverFetchRoute = (action: string) => { 9 | return `${isDev ? '/api' : window.location.pathname}?action=${action}`; 10 | }; 11 | export const serverFetch = async ( 12 | action: string, 13 | opts = {} 14 | ): Promise> => { 15 | const fetchOpts: RequestInit = { 16 | ...{ 17 | method: 'GET', 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | ...(WindowConfig.AUTHORIZATION 21 | ? { Authorization: WindowConfig.AUTHORIZATION || '' } 22 | : {}), 23 | }, 24 | cache: 'no-cache', 25 | credentials: 'omit', 26 | }, 27 | ...opts, 28 | }; 29 | const res = await fetch(serverFetchRoute(action), fetchOpts); 30 | return { 31 | status: res.status, 32 | data: res.ok ? await res.json().catch(() => null) : null, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/Components/DiskUsage/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components/index.tsx'; 5 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 6 | import { DiskUsageConstants } from './constants.ts'; 7 | import styles from './index.module.scss'; 8 | import { DiskUsageStore } from './store.ts'; 9 | export const DiskUsage: FC = observer(() => { 10 | const { pollData } = DiskUsageStore; 11 | const items = pollData?.items ?? []; 12 | if (!items.length) { 13 | return null; 14 | } 15 | return ( 16 | 17 |
18 | {items.map(({ id, free, total }) => ( 19 | 26 | ))} 27 |
28 |
29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /src/Components/Bootstrap/components/global.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // --x-html-bg: var(--x-fg); 3 | --x-fg: hsl(0 0% 10%); 4 | --x-body-fg: hsl(0 0% 10%); 5 | --x-body-bg: hsl(0 0% 90%); 6 | @media (prefers-color-scheme: dark) { 7 | // --x-html-bg: hsl(0, 0%, 0%); 8 | --x-fg: hsl(0 0% 90%); 9 | --x-body-fg: hsl(0 0% 90%); 10 | --x-body-bg: hsl(0 0% 0%); 11 | } 12 | } 13 | * { 14 | box-sizing: border-box; 15 | margin: 0; 16 | padding: 0; 17 | word-break: break-word; 18 | } 19 | html { 20 | // background: var(--x-html-bg); 21 | scroll-behavior: smooth; 22 | font-size: 85%; 23 | } 24 | body { 25 | display: grid; 26 | place-content: safe center; 27 | vertical-align: middle; 28 | gap: var(--x-gutter); 29 | margin: 0; 30 | background: var(--x-body-bg); 31 | padding: 0; 32 | color: var(--x-body-fg); 33 | line-height: 1.5; 34 | font-family: var(--x-code-font-family); 35 | } 36 | a { 37 | cursor: pointer; 38 | color: var(--x-fg); 39 | text-decoration: none; 40 | &:hover, 41 | &:active { 42 | color: var(--x-fg); 43 | text-decoration: underline; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); 4 | gap: var(--x-gutter); 5 | } 6 | .groupId { 7 | display: block; 8 | margin-bottom: calc(var(--x-gutter) * 0.5); 9 | text-align: center; 10 | text-decoration: underline; 11 | &:hover { 12 | text-decoration: none; 13 | } 14 | } 15 | .group { 16 | margin-bottom: calc(var(--x-gutter) * 0.5); 17 | } 18 | .groupMsg { 19 | display: flex; 20 | justify-content: center; 21 | } 22 | .groupNetworks { 23 | margin-bottom: var(--x-gutter); 24 | border-radius: var(--x-radius); 25 | background: var(--x-network-node-bg); 26 | padding: var(--x-gutter); 27 | color: var(--x-network-node-fg); 28 | } 29 | .groupNetwork { 30 | margin-bottom: calc(var(--x-gutter) * 0.5); 31 | border-bottom: 1px dashed var(--x-network-node-border-color); 32 | padding-bottom: calc(var(--x-gutter) * 0.5); 33 | &:last-child { 34 | margin-bottom: 0; 35 | border-bottom: 0; 36 | padding-bottom: 0; 37 | } 38 | &:hover { 39 | background: var(--x-network-node-row-bg); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { isDeepEqual } from '@/Components/Utils/components/is-deep-equal/index.ts'; 3 | import type { NetworkStatsPollDataProps } from './typings.ts';configure({ 4 | enforceActions: 'observed', 5 | });class Main { 6 | pollData: NetworkStatsPollDataProps | null = null; 7 | constructor() { 8 | makeAutoObservable(this); 9 | } 10 | setPollData(pollData: NetworkStatsPollDataProps | null) { 11 | if (isDeepEqual(pollData, this.pollData)) { 12 | return; 13 | } 14 | this.pollData = pollData; 15 | } 16 | get networks(): NetworkStatsPollDataProps['networks'] { 17 | return this.pollData?.networks ?? []; 18 | } 19 | get timestamp(): NetworkStatsPollDataProps['timestamp'] { 20 | return this.pollData?.timestamp ?? 0; 21 | } 22 | get sortNetworks() { 23 | return this.networks 24 | .filter(({ tx }) => Boolean(tx)) 25 | .toSorted((a, b) => a.tx - b.tx); 26 | } 27 | get networksCount() { 28 | return this.sortNetworks.length; 29 | } 30 | } 31 | export const NetworkStatsStore = new Main(); 32 | -------------------------------------------------------------------------------- /src/Components/ui/pie-chart/index.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-pip-chat-bg: hsl(0 0% 0% / 0.1); 3 | --x-pip-chat-fg-low: hsl(106, 87%, 41%); 4 | --x-pip-chat-fg-medium: hsl(49, 87%, 41%); 5 | --x-pip-chat-fg-high: hsl(0, 74%, 49%); 6 | --x-pip-chat-text-fg: hsl(0 0% 0%); 7 | @media (prefers-color-scheme: dark) { 8 | --x-pip-chat-bg: hsl(0 0% 100% / 0.1); 9 | --x-pip-chat-fg-low: hsl(106, 87%, 41%); 10 | --x-pip-chat-fg-medium: hsl(49, 87%, 41%); 11 | --x-pip-chat-fg-high: hsl(0, 74%, 49%); 12 | --x-pip-chat-text-fg: hsl(0 0% 100%); 13 | } 14 | } 15 | .pieBg { 16 | stroke: var(--x-pip-chat-bg); 17 | } 18 | .pieFg { 19 | transition: 20 | stroke-dashoffset 1s ease-out, 21 | stroke 1s ease-in-out; 22 | &[data-status="low"] { 23 | stroke: var(--x-pip-chat-fg-low); 24 | } 25 | &[data-status="medium"] { 26 | stroke: var(--x-pip-chat-fg-medium); 27 | } 28 | &[data-status="high"] { 29 | stroke: var(--x-pip-chat-fg-high); 30 | } 31 | } 32 | .pipText { 33 | fill: var(--x-pip-chat-text-fg); 34 | // font-weight: normal; 35 | // font-size: 1.5rem; 36 | font-family: var(--x-code-font-family); 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/Module/components/arrow.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-card-legend-arrow-fg: var(--x-card-legend-fg); 3 | --x-card-legend-arrow-bg-hover: hsl(0 0% 0% / 0.05); 4 | --x-card-legend-arrow-bg-active: hsl(0 0% 0% / 0.1); 5 | @media (prefers-color-scheme: dark) { 6 | --x-card-legend-arrow-fg: var(--x-card-legend-fg); 7 | --x-card-legend-arrow-bg-hover: hsl(0 0% 100% / 0.05); 8 | --x-card-legend-arrow-bg-active: hsl(0 0% 100% / 0.1); 9 | } 10 | } 11 | .arrow { 12 | display: flex; 13 | align-items: center; 14 | cursor: pointer; 15 | border: none; 16 | border-radius: var(--x-radius); 17 | background: transparent; 18 | padding: var(--x-gutter-sm); 19 | color: var(--x-card-legend-arrow-fg); 20 | &:hover { 21 | background: var(--x-card-legend-arrow-bg-hover); 22 | color: var(--x-card-legend-arrow-fg); 23 | } 24 | &:active { 25 | background: var(--x-card-legend-arrow-bg-active); 26 | color: var(--x-card-legend-arrow-fg); 27 | } 28 | &[data-disabled], 29 | &[data-disabled]:hover { 30 | opacity: 0.5; 31 | cursor: not-allowed; 32 | } 33 | svg { 34 | width: 1rem; 35 | height: 1rem; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/Location/LocationIpv4Action.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::FORBIDDEN) 21 | ->end(); 22 | } 23 | $ip = filter_input(\INPUT_GET, 'ip', \FILTER_VALIDATE_IP, [ 24 | 'flags' => \FILTER_FLAG_IPV4, 25 | ]); 26 | if ( ! $ip) { 27 | $response 28 | ->setStatus(StatusCode::BAD_REQUEST) 29 | ->end(); 30 | } 31 | $response 32 | ->setData(UtilsLocation::getLocation($ip)) 33 | ->end(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tools/po-parser.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @version 1.0.1 3 | */ 4 | 5 | import { existsSync, readFileSync } from 'node:fs'; 6 | import gettextParser from 'gettext-parser'; 7 | 8 | const PARSE_REGEX = 9 | /(msgctxt\s+"(.+?)"\s+)?msgid\s+"(.+?)"\s+msgstr\s+"(.+?)"/gm; 10 | export class PoParser { 11 | poPath = ''; 12 | items = {}; 13 | constructor({ poPath }) { 14 | this.poPath = poPath; 15 | if (!existsSync(this.poPath)) { 16 | throw new Error(`${this.poPath} not exists`); 17 | } 18 | } 19 | parse = () => { 20 | const input = readFileSync(this.poPath); 21 | const po = gettextParser.po.parse(input); 22 | for (const group of Object.values(po.translations)) { 23 | for (const item of Object.values(group)) { 24 | const id = item.msgid; 25 | const str = item.msgstr[0]; 26 | const ctxt = item?.msgctxt || ''; 27 | const key = ctxt !== '' ? `${ctxt}|${id}` : id; 28 | this.items[key] = str; 29 | } 30 | } 31 | this.items = Object.keys(this.items) 32 | .sort() 33 | .reduce((r, k) => { 34 | r[k] = this.items[k]; 35 | return r; 36 | }, {}); 37 | return this.items; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/usage.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, HTMLProps, ReactNode } from 'react'; 2 | import { MeterCore } from '@/Components/Meter/components/index.tsx'; 3 | import styles from './usage.module.scss'; 4 | export const NodesUsage: FC<{ children: ReactNode; percent: number }> = ({ 5 | children, 6 | percent, 7 | }) => { 8 | return ( 9 |
10 | {children} 11 |
{percent}%
12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | export const NodesUsageLabel: FC<{ children: ReactNode }> = (props) => { 19 | return
; 20 | }; 21 | export const NodesUsageChart: FC<{ children: ReactNode }> = (props) => { 22 | return
; 23 | }; 24 | export const NodesUsageOverview: FC> = (props) => { 25 | return
; 26 | }; 27 | // export const NodesUsagePercent: FC<{ percent: number }> = ({ percent }) => { 28 | // return
{percent}%
29 | // } 30 | -------------------------------------------------------------------------------- /src/Components/Config/ConfigPoll.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'APP_VERSION' => $config['APP_VERSION'], 14 | 'APP_NAME' => $config['APP_NAME'], 15 | 'APP_URL' => $config['APP_URL'], 16 | 'AUTHOR_URL' => $config['AUTHOR_URL'], 17 | 'UPDATE_PHP_URLS' => $config['UPDATE_PHP_URLS'], 18 | 'APP_CONFIG_URLS' => $config['APP_CONFIG_URLS'], 19 | 'APP_CONFIG_URL_DEV' => $config['APP_CONFIG_URL_DEV'], 20 | 'APP_TEMPERATURE_SENSOR_URL' => $config['APP_TEMPERATURE_SENSOR_URL'], 21 | 'APP_TEMPERATURE_SENSOR_PORTS' => $config['APP_TEMPERATURE_SENSOR_PORTS'], 22 | 'AUTHOR_NAME' => $config['AUTHOR_NAME'], 23 | 'LATEST_PHP_STABLE_VERSION' => $config['LATEST_PHP_STABLE_VERSION'], 24 | 'LATEST_NGINX_STABLE_VERSION' => $config['LATEST_NGINX_STABLE_VERSION'], 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { isDeepEqual } from '@/Components/Utils/components/is-deep-equal/index.ts'; 3 | import type { NodesItemProps, NodesPollDataProps } from './typings.ts';configure({ 4 | enforceActions: 'observed', 5 | }); 6 | class Main { 7 | readonly DEFAULT_ITEM = { 8 | id: '', 9 | url: '', 10 | fetchUrl: '', 11 | loading: true, 12 | status: 204, 13 | data: null, 14 | }; 15 | items: NodesItemProps[] = []; 16 | pollData: NodesPollDataProps | null = null; 17 | constructor() { 18 | makeAutoObservable(this); 19 | } 20 | setPollData = (pollData: NodesPollDataProps | null) => { 21 | if (isDeepEqual(pollData, this.pollData)) { 22 | return; 23 | } 24 | this.pollData = pollData; 25 | }; 26 | setItems = (items: NodesItemProps[]) => { 27 | this.items = items; 28 | }; 29 | setItem = ({ id, ...props }: Partial) => { 30 | const i = this.items.findIndex((item) => item.id === id); 31 | if (i === -1) { 32 | return; 33 | } 34 | this.items[i] = { ...this.items[i], ...props }; 35 | }; 36 | } 37 | export const NodesStore = new Main(); 38 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/disk.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import type { DiskUsageItemProps } from '@/Components/DiskUsage/components/typings.ts'; 3 | import type { PollDataProps } from '@/Components/Poll/components/typings.ts'; 4 | import { formatBytes } from '@/Components/Utils/components/format-bytes.ts'; 5 | import styles from './disk.module.scss'; 6 | import { NodesUsage, NodesUsageLabel, NodesUsageOverview } from './usage.tsx';const Disk: FC = memo(({ id, free, total }) => { 7 | return ( 8 |
9 | 10 | {`🖴 ${id}`} 11 | {`${formatBytes(free)} / ${formatBytes(total)}`} 12 | 13 |
14 | ); 15 | }); 16 | export const NodesDisk: FC<{ data: PollDataProps['diskUsage'] }> = ({ 17 | data, 18 | }) => { 19 | const items = data?.items ?? []; 20 | return ( 21 |
22 | {items.map(({ id, free, total }) => ( 23 | 24 | ))} 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/Components/ColorScheme/components/config-dark.scss: -------------------------------------------------------------------------------- 1 | @mixin config { 2 | @media (prefers-color-scheme: dark) { 3 | :root { 4 | --x-fg: hsl(0, 0%, 80%); 5 | --x-bg: hsl(0, 0%, 0%); 6 | 7 | --x-app-border-color: var(--x-bg); 8 | --x-app-bg: hsl(0, 0%, 13%); 9 | 10 | // star me 11 | --x-star-me-fg: var(--x-fg); 12 | --x-star-me-bg: var(--x-bg); 13 | --x-star-me-hover-fg: hsl(0, 0%, 100%); 14 | --x-star-me-hover-bg: var(--x-bg); 15 | --x-star-me-border-color: linear-gradient(90deg, transparent, hsl(0, 0%, 100%), transparent); 16 | // alert 17 | --x-status-ok-fg: hsl(0, 0%, 100%); 18 | --x-status-ok-bg: linear-gradient(hsl(120, 100%, 20%), hsl(120, 100%, 25%)); 19 | --x-status-error-fg: hsl(0, 0%, 100%); 20 | --x-status-error-bg: linear-gradient(hsl(0, 0%, 27%), hsl(0, 0%, 33%)); 21 | // progress 22 | 23 | // network node 24 | --x-network-node-fg: var(--x-fg); 25 | --x-network-node-bg: hsla(0, 0%, 100%, 0.05); 26 | --x-network-node-border-color: var(--x-card-split-color); 27 | --x-network-node-row-bg: var(--x-card-bg-hover); 28 | // ping 29 | 30 | // sys load 31 | // toast 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Components/Utils/components/use-ip.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { OK } from '@/Components/Rest/http-status.ts'; 4 | 5 | interface UseIpProps { 6 | ip: string; 7 | msg: string; 8 | isLoading: boolean; 9 | } 10 | export const useIp = (type: 4 | 6): UseIpProps => { 11 | const [data, setData] = useState({ 12 | ip: '', 13 | msg: gettext('Loading...'), 14 | isLoading: true, 15 | }); 16 | useEffect(() => { 17 | const fetchData = async () => { 18 | const res = await fetch(`https://ipv${type}.inn-studio.com/ip/?json`); 19 | await res 20 | .json() 21 | .catch(() => { 22 | setData({ ip: '', msg: gettext('Not support'), isLoading: false }); 23 | }) 24 | .then((ipData) => { 25 | if (ipData?.ip && res.status === OK) { 26 | setData({ ip: ipData.ip, msg: '', isLoading: false }); 27 | return; 28 | } 29 | setData({ 30 | ip: '', 31 | msg: gettext('Can not fetch IP'), 32 | isLoading: false, 33 | }); 34 | }); 35 | }; 36 | fetchData(); 37 | }, [type]); 38 | return data; 39 | }; 40 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/ServerInfoLocationIpv4Action.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::FORBIDDEN) 21 | ->end(); 22 | } 23 | $response = new RestResponse(); 24 | $ip = UtilsServerIp::getPublicIpV4(); 25 | if ( ! $ip) { 26 | $response 27 | ->setStatus(StatusCode::INTERNAL_SERVER_ERROR) 28 | ->end(); 29 | } 30 | $response 31 | ->setData(UtilsLocation::getLocation($ip)) 32 | ->end(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Components/Module/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import { type FC, useEffect } from 'react'; 3 | import { ModulePriority } from '@/Components/Module/components/priority.ts'; 4 | import styles from './index.module.scss'; 5 | import { ModulePreset } from './preset.ts'; 6 | import { ModuleStorage } from './storage.ts'; 7 | import { ModuleStore } from './store.ts'; 8 | import type { SortedModuleProps } from './typings.ts'; 9 | export const Modules: FC = observer(() => { 10 | const { setSortedModules, availableModules } = ModuleStore; 11 | useEffect(() => { 12 | const storageItems = ModuleStorage.getItems(); 13 | const sorted: SortedModuleProps[] = []; 14 | for (const preset of ModulePreset.items) { 15 | sorted.push({ 16 | id: preset.id, 17 | priority: 18 | Number(storageItems?.[preset.id]) || 19 | ModulePriority.indexOf(preset.id), 20 | }); 21 | } 22 | setSortedModules(sorted); 23 | }, [setSortedModules]); 24 | if (!availableModules.length) { 25 | return null; 26 | } 27 | return ( 28 |
29 | {availableModules.map(({ id, content: C }) => { 30 | return ; 31 | })} 32 |
33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /src/Components/Database/DatabasePoll.php: -------------------------------------------------------------------------------- 1 | null, 17 | ]; 18 | } 19 | $sqlite3Version = class_exists('SQLite3') ? SQLite3::version() : false; 20 | 21 | return [ 22 | $id => [ 23 | 'sqlite3' => $sqlite3Version ? $sqlite3Version['versionString'] : false, 24 | 'mysqliClientVersion' => \function_exists('mysqli_get_client_version') ? mysqli_get_client_version() : false, 25 | 'mongo' => class_exists('Mongo'), 26 | 'mongoDb' => class_exists('MongoDB'), 27 | 'postgreSql' => \function_exists('pg_connect'), 28 | 'paradox' => \function_exists('px_new'), 29 | 'msSql' => \function_exists('sqlsrv_server_info'), 30 | 'pdo' => class_exists('PDO') ? implode(',', PDO::getAvailableDrivers()) : false, 31 | ], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Components/Utils/UtilsTime.php: -------------------------------------------------------------------------------- 1 | 0, 26 | 'hours' => 0, 27 | 'mins' => 0, 28 | 'secs' => 0, 29 | ]; 30 | } 31 | $str = file_get_contents($filePath); 32 | $num = (float) $str; 33 | $secs = (int) fmod($num, 60); 34 | $num = (int) ($num / 60); 35 | $mins = (int) $num % 60; 36 | $num = (int) ($num / 60); 37 | $hours = (int) $num % 24; 38 | $num = (int) ($num / 24); 39 | $days = (int) $num; 40 | 41 | return [ 42 | 'days' => $days, 43 | 'hours' => $hours, 44 | 'mins' => $mins, 45 | 'secs' => $secs, 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Components/Module/components/arrow.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDown, ChevronUp } from 'lucide-react'; 2 | import { observer } from 'mobx-react-lite'; 3 | import { type FC, type MouseEvent, useCallback } from 'react'; 4 | import { gettext } from '@/Components/Language/index.ts'; 5 | import styles from './arrow.module.scss'; 6 | import { ModuleStore } from './store.ts'; 7 | export const ModuleArrow: FC<{ 8 | isDown: boolean; 9 | id: string; 10 | }> = observer(({ isDown, id }) => { 11 | const { disabledMoveUpId, disabledMoveDownId, moveUp, moveDown } = 12 | ModuleStore; 13 | const disabled = isDown ? disabledMoveDownId === id : disabledMoveUpId === id; 14 | const handleMove = useCallback( 15 | (e: MouseEvent) => { 16 | e.preventDefault(); 17 | e.stopPropagation(); 18 | if (isDown) { 19 | moveDown(id); 20 | return; 21 | } 22 | moveUp(id); 23 | }, 24 | [isDown, moveDown, moveUp, id] 25 | ); 26 | return ( 27 | 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/Components/Utils/UtilsClientIp.php: -------------------------------------------------------------------------------- 1 | \FILTER_FLAG_IPV4, 17 | ]); 18 | if ($ip) { 19 | return $ip; 20 | } 21 | } 22 | 23 | return ''; 24 | } 25 | 26 | public static function getV6() 27 | { 28 | $keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR']; 29 | foreach ($keys as $key) { 30 | if ( ! isset($_SERVER[$key])) { 31 | continue; 32 | } 33 | $ip = array_filter(explode(',', $_SERVER[$key])); 34 | $ip = filter_var(end($ip), \FILTER_VALIDATE_IP, [ 35 | 'flags' => \FILTER_FLAG_IPV6, 36 | ]); 37 | if ($ip) { 38 | return $ip; 39 | } 40 | } 41 | 42 | return ''; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Components/Poll/PollAction.php: -------------------------------------------------------------------------------- 1 | render()); 34 | } 35 | (new RestResponse()) 36 | ->setData($data) 37 | ->end(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Components/Updater/UpdaterActionVersion.php: -------------------------------------------------------------------------------- 1 | setData([ 33 | 'version' => $data['APP_VERSION'], 34 | ]) 35 | ->end(); 36 | } 37 | $response 38 | ->setStatus(StatusCode::NO_CONTENT) 39 | ->end(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Components/ui/error/index.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-error-fg: hsl(0 100% 50%); 3 | --x-error-bg: hsl(0 100% 30%); 4 | --x-error-icon-fg: hsl(0 100% 50%); 5 | --x-error-icon-bg: hsl(0 100% 97%); 6 | @media (prefers-color-scheme: dark) { 7 | --x-error-fg: hsl(0 0% 100% / 0.9); 8 | --x-error-bg: hsl(0, 100%, 50%); 9 | --x-error-icon-fg: var(--x-error-bg); 10 | --x-error-icon-bg: hsl(0 0% 100% / 0.5); 11 | } 12 | } 13 | .main { 14 | display: flex; 15 | position: relative; 16 | align-items: center; 17 | gap: var(--x-gutter-sm); 18 | border-radius: var(--x-radius); 19 | // background: var(--x-error-bg); 20 | color: var(--x-error-fg); 21 | font-family: var(--x-text-font-family); 22 | &::before { 23 | border-radius: var(--x-radius); 24 | // position: absolute; 25 | // top: 50%; 26 | // left: 0; 27 | // transform: translateY(-50%); 28 | background: var(--x-error-bg); 29 | width: 2px; 30 | height: 50%; 31 | content: ""; 32 | } 33 | // &::before { 34 | // display: flex; 35 | // justify-content: center; 36 | // align-items: center; 37 | // border-radius: 50%; 38 | // background: var(--x-error-icon-bg); 39 | // width: 1rem; 40 | // height: 1rem; 41 | // content: "×"; 42 | // color: var(--x-error-icon-fg); 43 | // font-weight: bold; 44 | // font-size: 1rem; 45 | // } 46 | } 47 | -------------------------------------------------------------------------------- /src/Components/Utils/UtilsNetwork.php: -------------------------------------------------------------------------------- 1 | $lineArr[0], 39 | 'rx' => $rx, 40 | 'tx' => $tx, 41 | ]; 42 | } 43 | 44 | return $eths; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Components/Events/EventsApi.php: -------------------------------------------------------------------------------- 1 | $priority, 20 | self::$CALLBACK_ID => $callback, 21 | ]; 22 | } 23 | 24 | public static function emit() 25 | { 26 | $args = \func_get_args(); 27 | $name = $args[0]; 28 | $return = isset($args[1]) ? $args[1] : null; 29 | unset($args[0], $args[1]); 30 | $events = isset(self::$events[$name]) ? self::$events[$name] : false; 31 | if ( ! $events) { 32 | return $return; 33 | } 34 | $sortArr = []; 35 | foreach ($events as $k => $filter) { 36 | $sortArr[$k] = $filter[self::$PRIORITY_ID]; 37 | } 38 | array_multisort($sortArr, $events); 39 | foreach ($events as $filter) { 40 | $return = \call_user_func_array($filter[self::$CALLBACK_ID], [$return, $args]); 41 | } 42 | 43 | return $return; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/network.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import { NetworksStatsItem } from '@/Components/NetworkStats/components/item.tsx'; 3 | import type { NetworkStatsPollDataProps } from '@/Components/NetworkStats/components/typings.ts'; 4 | import { usePrevious } from '@/Components/Utils/components/use-previous.ts'; 5 | import styles from './network.module.scss'; 6 | export const NodesNetworkStats: FC<{ data: NetworkStatsPollDataProps }> = ({ 7 | data, 8 | }) => { 9 | const { networks, timestamp } = data; 10 | const prevData = usePrevious({ 11 | items: networks, 12 | timestamp, 13 | }); 14 | const seconds = timestamp - (prevData?.timestamp || timestamp); 15 | return ( 16 |
17 | {networks.map(({ id, rx, tx }) => { 18 | if (!(rx || tx)) { 19 | return null; 20 | } 21 | const prevItem = (prevData?.items || networks).find( 22 | (item) => item.id === id 23 | ); 24 | const prevRx = prevItem?.rx || 0; 25 | const prevTx = prevItem?.tx || 0; 26 | return ( 27 | 35 | ); 36 | })} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/item.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { formatBytes } from '@/Components/Utils/components/format-bytes'; 4 | import { template } from '@/Components/Utils/components/template.ts'; 5 | import styles from './item.module.scss';interface NetworksStatsItemProps { 6 | id: string; 7 | totalRx: number; 8 | rateRx: number; 9 | totalTx: number; 10 | rateTx: number; 11 | } 12 | export const NetworksStatsItem: FC = ({ 13 | id, 14 | totalRx = 0, 15 | rateRx = 0, 16 | totalTx = 0, 17 | rateTx = 0, 18 | }) => { 19 | if (!id) { 20 | return null; 21 | } 22 | return ( 23 |
24 |
{id}
25 |
26 |
27 | {template(gettext('Recived: {{total}}'), { 28 | total: formatBytes(totalRx), 29 | })} 30 |
31 |
{formatBytes(rateRx)}/s
32 |
33 |
34 |
35 | {template(gettext('Sent: {{total}}'), { 36 | total: formatBytes(totalTx), 37 | })} 38 |
39 |
{formatBytes(rateTx)}/s
40 |
41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/Components/Nodes/components/cpu.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, memo } from 'react'; 2 | import { gettext } from '@/Components/Language/index.ts'; 3 | import { SysLoadItem } from '@/Components/ServerStatus/components/system-load.tsx'; 4 | import type { ServerStatusPollDataProps } from '@/Components/ServerStatus/components/typings.ts'; 5 | import styles from './cpu.module.scss'; 6 | import { NodesUsage, NodesUsageLabel, NodesUsageOverview } from './usage.tsx'; 7 | 8 | const SysLoad: FC<{ 9 | items: number[]; 10 | }> = ({ items }) => { 11 | return ( 12 |
13 | {items.map((n) => ( 14 | 15 | ))} 16 |
17 | ); 18 | }; 19 | export const NodesCpu: FC<{ 20 | sysLoad: ServerStatusPollDataProps['sysLoad']; 21 | cpuUsage: ServerStatusPollDataProps['cpuUsage']; 22 | }> = memo(({ sysLoad, cpuUsage }) => { 23 | const { user, idle, sys, usage } = cpuUsage; 24 | const cpuTotal = user + idle + sys; 25 | const cpuTitle = ` 26 | user: ${((user / cpuTotal) * 100).toFixed(2)}% 27 | idle: ${((idle / cpuTotal) * 100).toFixed(2)}% 28 | sys: ${((sys / cpuTotal) * 100).toFixed(2)}% 29 | `; 30 | return ( 31 | 32 | {gettext('CPU')} 33 | 34 | 35 | 36 | 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/Components/Updater/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import { ConfigStore } from '@/Components/Config/store.ts'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { template } from '@/Components/Utils/components/template'; 5 | 6 | configure({ 7 | enforceActions: 'observed', 8 | }); 9 | class Main { 10 | isUpdating = false; 11 | isUpdateError = false; 12 | targetVersion = ''; 13 | constructor() { 14 | makeAutoObservable(this); 15 | } 16 | setTargetVersion = (targetVersion: string) => { 17 | this.targetVersion = targetVersion; 18 | }; 19 | setIsUpdating = (isUpdating: boolean) => { 20 | this.isUpdating = isUpdating; 21 | }; 22 | setIsUpdateError = (isUpdateError: boolean) => { 23 | this.isUpdateError = isUpdateError; 24 | }; 25 | get notiText(): string { 26 | if (this.isUpdating) { 27 | return gettext('⏳ Updating, please wait a second...'); 28 | } 29 | if (this.isUpdateError) { 30 | return gettext('❌ Update error, click here to try again?'); 31 | } 32 | if (this.targetVersion) { 33 | return template( 34 | gettext('✨ Found new version: {{oldVersion}} ⇢ {{newVersion}}'), 35 | { 36 | oldVersion: ConfigStore.pollData?.APP_VERSION ?? '-', 37 | newVersion: this.targetVersion, 38 | } 39 | ); 40 | } 41 | return ''; 42 | } 43 | } 44 | export const UpdaterStore = new Main(); 45 | -------------------------------------------------------------------------------- /src/Components/ColorScheme/components/config.scss: -------------------------------------------------------------------------------- 1 | @use "./comm.scss" as comm; 2 | @use "./config-dark.scss" as dark; 3 | :root { 4 | --x-max-width: 1680px; 5 | --x-radius: 0.5rem; 6 | --x-fg: hsl(0, 0%, 20%); 7 | --x-bg: hsl(0, 0%, 97%); 8 | --x-text-font-family: Verdana, Geneva, Tahoma, sans-serif; 9 | --x-code-font-family: monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New"; 10 | 11 | --x-app-border-color: var(--x-fg); 12 | --x-app-bg: var(--x-bg); 13 | 14 | // star me 15 | --x-star-me-fg: var(--x-bg); 16 | --x-star-me-bg: var(--x-fg); 17 | --x-star-me-hover-fg: hsl(0, 0%, 100%); 18 | --x-star-me-hover-bg: var(--x-fg); 19 | --x-star-me-border-color: linear-gradient(90deg, transparent, hsl(0, 0%, 100%), transparent); 20 | // alert 21 | --x-status-ok-fg: hsl(0, 0%, 100%); 22 | --x-status-ok-bg: linear-gradient(hsl(120, 100%, 30%), hsl(120, 100%, 45%)); 23 | --x-status-error-fg: hsl(0, 0%, 100%); 24 | --x-status-error-bg: linear-gradient(hsl(0, 0%, 50%), hsl(0, 0%, 73%)); 25 | // progress 26 | // network stats 27 | 28 | // network node 29 | --x-network-node-fg: var(--x-fg); 30 | --x-network-node-bg: hsla(132, 4%, 23%, 0.1); 31 | --x-network-node-border-color: var(--x-card-split-color); 32 | --x-network-node-row-bg: linear-gradient(to right, transparent, hsla(0, 0%, 100%, 0.5), transparent); 33 | 34 | // sys load 35 | 36 | // toast 37 | } 38 | @include comm.comm(); 39 | @include dark.config(); 40 | -------------------------------------------------------------------------------- /src/Components/Button/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { AlertTriangle, LoaderPinwheel, Pointer, X } from 'lucide-react'; 2 | import type { AnchorHTMLAttributes, ButtonHTMLAttributes, FC } from 'react'; 3 | import styles from './index.module.scss'; 4 | import { ButtonStatus, type ButtonStatusValue } from './typings.ts';interface ButtonProps extends ButtonHTMLAttributes { 5 | status?: ButtonStatusValue; 6 | } 7 | interface LinkProps extends AnchorHTMLAttributes { 8 | status?: ButtonStatusValue; 9 | } 10 | const Icon: FC<{ status: ButtonStatusValue }> = ({ status }) => { 11 | return ( 12 | 13 | {{ 14 | [ButtonStatus.Error]: , 15 | [ButtonStatus.Loading]: , 16 | [ButtonStatus.Warning]: , 17 | [ButtonStatus.Pointer]: , 18 | }?.[status] ?? null} 19 | 20 | ); 21 | }; 22 | export const Button: FC = ({ 23 | status = ButtonStatus.Pointer, 24 | children, 25 | ...props 26 | }) => { 27 | return ( 28 | 32 | ); 33 | }; 34 | export const Link: FC = ({ 35 | status = ButtonStatus.Pointer, 36 | children, 37 | ...props 38 | }) => { 39 | return ( 40 | 41 | 42 | {children} 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/Components/Module/components/preset.ts: -------------------------------------------------------------------------------- 1 | import { BrowserBenchmarkLoader } from '@/Components/BrowserBenchmark/components/loader.ts'; 2 | import { DatabaseLoader } from '@/Components/Database/components/loader.ts'; 3 | import { DiskUsageLoader } from '@/Components/DiskUsage/components/loader.ts'; 4 | import { MyInfoLoader } from '@/Components/MyInfo/components/loader.ts'; 5 | import { NetworkStatsLoader } from '@/Components/NetworkStats/components/loader.ts'; 6 | import { NodesLoader } from '@/Components/Nodes/components/loader.ts'; 7 | import { PhpExtensionsLoader } from '@/Components/PhpExtensions/components/loader.ts'; 8 | import { PhpInfoLoader } from '@/Components/PhpInfo/components/loader.ts'; 9 | import { PingLoader } from '@/Components/Ping/components/loader.ts'; 10 | import { ServerBenchmarkLoader } from '@/Components/ServerBenchmark/components/loader.ts'; 11 | import { ServerInfoLoader } from '@/Components/ServerInfo/components/loader.ts'; 12 | import { ServerStatusLoader } from '@/Components/ServerStatus/components/loader.ts'; 13 | import { TemperatureSensorLoader } from '@/Components/TemperatureSensor/components/loader.ts'; 14 | export const ModulePreset = { 15 | items: [ 16 | NodesLoader, 17 | TemperatureSensorLoader, 18 | ServerStatusLoader, 19 | NetworkStatsLoader, 20 | DiskUsageLoader, 21 | PingLoader, 22 | ServerInfoLoader, 23 | PhpInfoLoader, 24 | PhpExtensionsLoader, 25 | DatabaseLoader, 26 | ServerBenchmarkLoader, 27 | BrowserBenchmarkLoader, 28 | MyInfoLoader, 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": [ 5 | "./src/*" 6 | ] 7 | }, 8 | "allowImportingTsExtensions": true, 9 | "allowJs": false, 10 | "allowSyntheticDefaultImports": true, 11 | "alwaysStrict": true, 12 | "baseUrl": ".", 13 | "declaration": true, 14 | "diagnostics": true, 15 | "downlevelIteration": true, 16 | "emitBOM": false, 17 | "emitDecoratorMetadata": true, 18 | "esModuleInterop": true, 19 | "experimentalDecorators": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "isolatedModules": true, 22 | "jsx": "react-jsx", 23 | "jsxImportSource": "react", 24 | "lib": [ 25 | "ESNext", 26 | "DOM" 27 | ], 28 | "module": "ESNext", 29 | "moduleResolution": "node", 30 | "newLine": "LF", 31 | "noEmit": true, 32 | "noEmitOnError": true, 33 | "noImplicitAny": true, 34 | "noImplicitReturns": true, 35 | "noImplicitThis": true, 36 | "noUnusedLocals": true, 37 | "pretty": true, 38 | "resolveJsonModule": true, 39 | "skipLibCheck": true, 40 | "sourceMap": true, 41 | "strict": true, 42 | "strictNullChecks": true, 43 | "target": "ESNext", 44 | "incremental": false, 45 | "plugins": [ 46 | { 47 | "name": "typescript-plugin-css-modules" 48 | } 49 | ] 50 | }, 51 | "include": [ 52 | "src/**/*" 53 | ], 54 | "exclude": [ 55 | "node_modules", 56 | "lib" 57 | ], 58 | "includes": [ 59 | "src/scss.d.ts" 60 | ] 61 | } -------------------------------------------------------------------------------- /src/Components/UserConfig/UserConfigApi.php: -------------------------------------------------------------------------------- 1 | { 9 | const env = loadEnv(mode, process.cwd(), ''); 10 | return { 11 | root: './dev', 12 | envDir: './', 13 | server: { 14 | proxy: { 15 | '/api': { 16 | target: 'http://localhost:8000/api.php', 17 | changeOrigin: true, 18 | rewrite: (path) => path.replace(REGEX, ''), 19 | }, 20 | }, 21 | }, 22 | resolve: { 23 | alias: { 24 | '@': `${dirname(fileURLToPath(import.meta.url))}/src`, 25 | }, 26 | }, 27 | css: { 28 | modules: { 29 | generateScopedName: '[name]__[local]_[hash]', 30 | }, 31 | }, 32 | plugins: [react(), tsconfigPaths()], 33 | build: { 34 | outDir: '../dist', 35 | manifest: true, 36 | target: 'esnext', 37 | // rollupOptions: { 38 | // external: ['react', 'react-dom'], 39 | // output: { 40 | // globals: { 41 | // react: 'React', 42 | // 'react-dom': 'ReactDOM', 43 | // }, 44 | // }, 45 | // }, 46 | 47 | // rollupOptions: { 48 | // input: new URL('./src/main.tsx', import.meta.url).pathname, 49 | // }, 50 | }, 51 | define: { 52 | VITE_PORT: JSON.stringify(env.VITE_PORT), 53 | }, 54 | }; 55 | }); 56 | -------------------------------------------------------------------------------- /src/Components/TemperatureSensor/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import type { FC } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { Meter } from '@/Components/Meter/components'; 5 | import { ModuleGroup } from '@/Components/Module/components/group.tsx'; 6 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 7 | import { template } from '@/Components/Utils/components/template'; 8 | import { UiSingleColContainer } from '@/Components/ui/col/single-container.tsx'; 9 | import { TemperatureSensorConstants } from './constants.ts'; 10 | import { TemperatureSensorStore } from './store.ts'; 11 | export const TemperatureSensor: FC = observer(() => { 12 | const { pollData } = TemperatureSensorStore; 13 | if (!pollData?.items?.length) { 14 | return null; 15 | } 16 | const { items } = pollData; 17 | return ( 18 | 22 | 23 | {items.map(({ id, name, celsius }) => ( 24 | 30 | 36 | 37 | ))} 38 | 39 | 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /src/Components/ui/pie-chart/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import styles from './index.module.scss'; 3 | import type { PieChartStatusKey } from './typings.ts'; 4 | export const PieChart: FC<{ 5 | percent: number; 6 | status: PieChartStatusKey; 7 | size?: number; 8 | stockWidth?: number; 9 | fontSize?: number; 10 | }> = ({ percent, status, size = 25, stockWidth = 2, fontSize = 10 }) => { 11 | const circumference = 2 * Math.PI * size; 12 | const strokeDashoffset = circumference - (percent / 100) * circumference; 13 | return ( 14 | 19 | Pie chart 20 | 28 | 40 | 48 | {percent}% 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/Components/ServerInfo/ServerInfoPoll.php: -------------------------------------------------------------------------------- 1 | null, 19 | ]; 20 | } 21 | 22 | return [ 23 | $id => [ 24 | 'serverName' => $this->getServerInfo('SERVER_NAME'), 25 | 'serverUtcTime' => UtilsTime::getUtcTime(), 26 | 'localIpv4' => UtilsServerIp::getLocalIpv4(), 27 | 'localIpv6' => UtilsServerIp::getLocalIpv6(), 28 | 'serverTime' => UtilsTime::getTime(), 29 | 'serverUptime' => UtilsTime::getUptime(), 30 | 'serverSoftware' => $this->getServerInfo('SERVER_SOFTWARE'), 31 | 'phpVersion' => \PHP_VERSION, 32 | 'cpuModel' => UtilsCpu::getModel(), 33 | 'serverOs' => php_uname(), 34 | 'scriptPath' => __FILE__, 35 | 'diskUsage' => UtilsDisk::getItems(), 36 | ], 37 | ]; 38 | } 39 | 40 | private function getServerInfo($key) 41 | { 42 | return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Components/Nav/components/index.module.scss: -------------------------------------------------------------------------------- 1 | @use "@/Components/Style/components//device.scss" as m; 2 | :root { 3 | --x-nav-fg: hsl(0 0% 100% / 0.9); 4 | --x-nav-bg: hsl(0 0% 15% / 0.95); 5 | --x-nav-bg-hover: hsl(0 0% 100% / 0.05); 6 | --x-nav-bg-active: hsl(0 0% 100% / 0.1); 7 | --x-nav-border-color: hsl(0 0% 100% / 0.05); 8 | @media (prefers-color-scheme: dark) { 9 | --x-nav-fg: hsl(0 0% 95% / 0.95); 10 | --x-nav-bg: hsl(0 0% 20% / 0.95); 11 | --x-nav-bg-hover: hsl(0 0% 25% / 0.95); 12 | --x-nav-bg-active: hsl(0 0% 30% / 0.95); 13 | --x-nav-border-color: hsl(0 0% 100% / 0.05); 14 | } 15 | } 16 | .main { 17 | display: flex; 18 | position: sticky; 19 | // right: 0; 20 | bottom: 0; 21 | // left: 0; 22 | justify-content: flex-start; 23 | align-items: center; 24 | z-index: 10; 25 | background: var(--x-nav-bg); 26 | // height: 3rem; 27 | overflow-x: auto; 28 | // line-height: 3rem; 29 | @include m.device(lg) { 30 | justify-content: center; 31 | border-radius: var(--x-radius) var(--x-radius) 0 0; 32 | } 33 | } 34 | .link { 35 | position: relative; 36 | border-right: 1px solid var(--x-nav-border-color); 37 | padding: var(--x-gutter); 38 | color: var(--x-nav-fg); 39 | white-space: nowrap; 40 | &:hover { 41 | background: var(--x-nav-bg-hover); 42 | color: var(--x-nav-fg); 43 | text-decoration: none; 44 | } 45 | &:focus, 46 | &:active, 47 | &[data-active] { 48 | background: var(--x-nav-bg-active); 49 | color: var(--x-nav-fg); 50 | text-decoration: none; 51 | } 52 | &:last-child { 53 | border-right: 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Components/UserConfig/typings.ts: -------------------------------------------------------------------------------- 1 | export type UserConfigDisableFeatureKey = 2 | | 'ServerStatus' 3 | | 'DiskUsage' 4 | | 'NetworkStats' 5 | | 'Ping' 6 | | 'ServerInfo' 7 | | 'PhpInfo' 8 | | 'PhpInfoDetail' 9 | | 'PhpDisabledFunctions' 10 | | 'PhpDisabledClasses' 11 | | 'PhpExtensions' 12 | | 'PhpExtensionsLoaded' 13 | | 'Database' 14 | | 'MyServerBenchmark' 15 | | 'MyInfo' 16 | | 'ServerIp'; 17 | export type UserConfigDisableFeatureValue = 18 | | 'serverStatus' 19 | | 'diskUsage' 20 | | 'networkStats' 21 | | 'ping' 22 | | 'serverInfo' 23 | | 'phpInfo' 24 | | 'phpInfoDetail' 25 | | 'phpDisabledFunctions' 26 | | 'phpDisabledClasses' 27 | | 'phpExtensions' 28 | | 'phpExtensionsLoaded' 29 | | 'database' 30 | | 'myServerBenchmark' 31 | | 'myInfo' 32 | | 'serverIp'; 33 | export const UserConfigDisableFeature = { 34 | ServerStatus: 'serverStatus', 35 | DiskUsage: 'diskUsage', 36 | NetworkStats: 'networkStats', 37 | Ping: 'ping', 38 | ServerInfo: 'serverInfo', 39 | PhpInfo: 'phpInfo', 40 | PhpInfoDetail: 'phpInfoDetail', 41 | PhpDisabledFunctions: 'phpDisabledFunctions', 42 | PhpDisabledClasses: 'phpDisabledClasses', 43 | PhpExtensions: 'phpExtensions', 44 | PhpExtensionsLoaded: 'phpExtensionsLoaded', 45 | Database: 'database', 46 | MyServerBenchmark: 'myServerBenchmark', 47 | MyInfo: 'myInfo', 48 | ServerIp: 'serverIp', 49 | }; 50 | export type UserConfigNodeProps = [nodeName: string, url: string]; 51 | export interface UserConfigProps { 52 | serverBenchmarkCd?: number; 53 | nodes?: UserConfigNodeProps[]; 54 | disabled?: UserConfigDisableFeatureKey; 55 | } 56 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/ServerBenchmarkPerformanceAction.php: -------------------------------------------------------------------------------- 1 | setStatus(StatusCode::FORBIDDEN) 19 | ->end(); 20 | } 21 | $this->renderMarks(); 22 | } 23 | 24 | private function renderMarks() 25 | { 26 | set_time_limit(0); 27 | $remainingSeconds = ServerBenchmarkApi::getRemainingSeconds(); 28 | $response = new RestResponse(); 29 | if ($remainingSeconds) { 30 | $response 31 | ->setStatus(StatusCode::TOO_MANY_REQUESTS) 32 | ->setData([ 33 | 'seconds' => $remainingSeconds, 34 | ]) 35 | ->end(); 36 | } 37 | ServerBenchmarkApi::setExpired(); 38 | ServerBenchmarkApi::setIsRunning(true); 39 | // start benchmark 40 | $marks = ServerBenchmarkApi::getPoints(); 41 | // end benchmark 42 | ServerBenchmarkApi::setIsRunning(false); 43 | $response 44 | ->setData([ 45 | 'marks' => $marks, 46 | ]) 47 | ->end(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Components/Database/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import { type FC, memo } from 'react'; 3 | import { gettext } from '@/Components/Language/index.ts'; 4 | import { ModuleGroup } from '@/Components/Module/components/group.tsx'; 5 | import { ModuleItem } from '@/Components/Module/components/item.tsx'; 6 | import { UiMultiColContainer } from '@/Components/ui/col/multi-container.tsx'; 7 | import { EnableStatus } from '@/Components/ui/enable-status/index.tsx'; 8 | import { DatabaseConstants } from './constants.ts'; 9 | import { DatabaseStore } from './store'; 10 | export const Database: FC = memo( 11 | observer(() => { 12 | const { pollData } = DatabaseStore; 13 | const shortItems: [string, boolean | string][] = [ 14 | ['SQLite3', pollData?.sqlite3 ?? false], 15 | ['MySQLi client', pollData?.mysqliClientVersion ?? false], 16 | ['Mongo', pollData?.mongo ?? false], 17 | ['MongoDB', pollData?.mongoDb ?? false], 18 | ['PostgreSQL', pollData?.postgreSql ?? false], 19 | ['Paradox', pollData?.paradox ?? false], 20 | ['MS SQL', pollData?.msSql ?? false], 21 | ['PDO', pollData?.pdo ?? false], 22 | ]; 23 | return ( 24 | 25 | 26 | {shortItems.map(([name, content]) => ( 27 | 28 | 29 | 30 | ))} 31 | 32 | 33 | ); 34 | }) 35 | ); 36 | -------------------------------------------------------------------------------- /src/Components/PhpInfo/components/php-version.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import { type FC, useEffect } from 'react'; 3 | import { Link } from '@/Components/Button/components/index.tsx'; 4 | import { serverFetch } from '@/Components/Fetch/server-fetch.ts'; 5 | import { gettext } from '@/Components/Language/index.ts'; 6 | import { OK } from '@/Components/Rest/http-status.ts'; 7 | import { template } from '@/Components/Utils/components/template.ts'; 8 | import { versionCompare } from '@/Components/Utils/components/version-compare.ts'; 9 | import { PhpInfoStore } from './store.ts'; 10 | export const PhpInfoPhpVersion: FC = observer(() => { 11 | const { pollData, latestPhpVersion, setLatestPhpVersion } = PhpInfoStore; 12 | useEffect(() => { 13 | const fetchData = async () => { 14 | const { data, status } = await serverFetch<{ version: string }>( 15 | 'latestPhpVersion' 16 | ); 17 | if (data?.version && status === OK) { 18 | setLatestPhpVersion(data.version); 19 | } 20 | }; 21 | fetchData(); 22 | }, [setLatestPhpVersion]); 23 | const phpVersion = pollData?.phpVersion ?? ''; 24 | const compare = versionCompare(phpVersion, latestPhpVersion); 25 | return ( 26 | 30 | {compare === -1 31 | ? ` ${template( 32 | gettext('{{oldVersion}} (Latest: {{latestPhpVersion}})'), 33 | { 34 | oldVersion: phpVersion, 35 | latestPhpVersion, 36 | } 37 | )}` 38 | : phpVersion} 39 | 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /src/Components/PhpInfo/PhpInfoPoll.php: -------------------------------------------------------------------------------- 1 | null, 15 | ]; 16 | } 17 | 18 | return [ 19 | $id => [ 20 | 'phpVersion' => \PHP_VERSION, 21 | 'sapi' => \PHP_SAPI, 22 | 'displayErrors' => (bool) \ini_get('display_errors'), 23 | 'errorReporting' => (int) \ini_get('error_reporting'), 24 | 'memoryLimit' => (string) \ini_get('memory_limit'), 25 | 'postMaxSize' => (string) \ini_get('post_max_size'), 26 | 'uploadMaxFilesize' => (string) \ini_get('upload_max_filesize'), 27 | 'maxInputVars' => (int) \ini_get('max_input_vars'), 28 | 'maxExecutionTime' => (int) \ini_get('max_execution_time'), 29 | 'defaultSocketTimeout' => (int) \ini_get('default_socket_timeout'), 30 | 'allowUrlFopen' => (bool) \ini_get('allow_url_fopen'), 31 | 'smtp' => (bool) \ini_get('SMTP'), 32 | 'disableFunctions' => UserConfigApi::isDisabled('phpDisabledFunctions') ? [] : array_filter(explode(',', (string) \ini_get('disable_functions'))), 33 | 'disableClasses' => UserConfigApi::isDisabled('phpDisabledClasses') ? [] : array_filter(explode(',', (string) \ini_get('disable_classes'))), 34 | ], 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /prod.vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync } from 'node:fs'; 2 | import { dirname, resolve } from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | import react from '@vitejs/plugin-react'; 5 | import { defineConfig } from 'vite'; 6 | import tsconfigPaths from 'vite-tsconfig-paths'; 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)); 9 | const tmpDir = resolve(__dirname, '.tmp'); 10 | if (!existsSync(tmpDir)) { 11 | mkdirSync(tmpDir); 12 | } 13 | export default defineConfig({ 14 | mode: 'production', 15 | root: __dirname, 16 | esbuild: { 17 | legalComments: 'none', 18 | }, 19 | build: { 20 | lib: { 21 | entry: resolve(__dirname, 'src/main.tsx'), 22 | name: 'app', 23 | fileName: () => 'app.js', 24 | formats: ['umd'], 25 | cssFileName: 'app', 26 | }, 27 | outDir: tmpDir, 28 | sourcemap: 'hidden', 29 | emptyOutDir: true, 30 | target: 'es2024', 31 | // rollupOptions:{ 32 | // output: { 33 | // assetFileNames: (assetInfo) => { 34 | // for(const name of assetInfo.names) { 35 | // if (name.endsWith('.css')) { 36 | // return 'app.css'; 37 | // } 38 | // } 39 | // } 40 | // } 41 | // } 42 | }, 43 | resolve: { 44 | alias: { 45 | '@': resolve(__dirname, 'src/'), 46 | }, 47 | extensions: ['.ts', '.tsx', '.js', '.mjs'], 48 | }, 49 | define: { 50 | __DEV__: false, 51 | DEBUG: false, 52 | 'process.env': { 53 | NODE_ENV: JSON.stringify('production'), 54 | WEBPACK_ENV: JSON.stringify('production'), 55 | }, 56 | }, 57 | plugins: [react(), tsconfigPaths()], 58 | }); 59 | -------------------------------------------------------------------------------- /src/Components/Action/Action.php: -------------------------------------------------------------------------------- 1 | delay(); 15 | $action = (string) filter_input(\INPUT_GET, 'action', \FILTER_DEFAULT); 16 | if ( ! $action) { 17 | return; 18 | } 19 | // for php54 20 | foreach ([ 21 | 'Poll\\PollAction', 22 | 'Script\\ScriptAction', 23 | 'Style\\StyleAction', 24 | 'Ping\\PingAction', 25 | 'ServerInfo\\ServerInfoPublicIpv4Action', 26 | 'ServerInfo\\ServerInfoPublicIpv6Action', 27 | 'PhpInfo\\PhpInfoLatestPhpVersionAction', 28 | 'PhpInfoDetail\\PhpInfoDetailAction', 29 | 'Updater\\UpdaterActionVersion', 30 | 'Updater\\UpdaterActionUpdate', 31 | 'ServerBenchmark\\ServerBenchmarkPerformanceAction', 32 | 'ServerBenchmark\\ServerBenchmarkServersAction', 33 | 'Location\\LocationIpv4Action', 34 | 'Nodes\\NodesAction', 35 | 'BrowserBenchmark\\BrowserBenchmarkBrowsersAction', 36 | ] as $fn) { 37 | $class = "\\InnStudio\\Prober\\Components\\{$fn}"; 38 | (new $class())->render($action); 39 | } 40 | (new RestResponse()) 41 | ->setStatus(StatusCode::BAD_REQUEST) 42 | ->end(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /compiler/StyleGeneration.php: -------------------------------------------------------------------------------- 1 | $this->styleFilePath, 15 | 'distFilePath' => $this->distFilePath, 16 | ] = $args; 17 | 18 | if ( ! is_file($this->styleFilePath)) { 19 | $this->die("File not found: {$this->styleFilePath}"); 20 | } 21 | 22 | if ( ! is_file($this->distFilePath)) { 23 | $this->die("File not found: {$this->distFilePath}"); 24 | } 25 | 26 | if ( ! $this->setStyle($this->getStyle())) { 27 | $this->die('Error: can not write script content to dist.'); 28 | } 29 | 30 | $this->die('Script content wrote successful.', false); 31 | } 32 | 33 | private function getStyle(): string 34 | { 35 | return (string) file_get_contents($this->styleFilePath); 36 | } 37 | 38 | private function setStyle(string $style): bool 39 | { 40 | $dist = (string) file_get_contents($this->distFilePath); 41 | 42 | if ( ! $dist) { 43 | return false; 44 | } 45 | 46 | $dist = str_replace('{{X_STYLE}}', $style, $dist); 47 | 48 | return (bool) file_put_contents($this->distFilePath, $dist); 49 | } 50 | 51 | private function die(string $msg, bool $die = true): void 52 | { 53 | $msg = "[StyleGeneration] {$msg}\n"; 54 | 55 | if ($die) { 56 | exit($msg); 57 | } 58 | 59 | echo $msg; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /compiler/ScriptGeneration.php: -------------------------------------------------------------------------------- 1 | $this->scriptFilePath, 15 | 'distFilePath' => $this->distFilePath, 16 | ] = $args; 17 | 18 | if ( ! is_file($this->scriptFilePath)) { 19 | $this->die("File not found: {$this->scriptFilePath}"); 20 | } 21 | 22 | if ( ! is_file($this->distFilePath)) { 23 | $this->die("File not found: {$this->distFilePath}"); 24 | } 25 | 26 | if ( ! $this->setScript($this->getScript())) { 27 | $this->die('Error: can not write script content to dist.'); 28 | } 29 | 30 | $this->die('Script content wrote successful.', false); 31 | } 32 | 33 | private function getScript(): string 34 | { 35 | return (string) file_get_contents($this->scriptFilePath); 36 | } 37 | 38 | private function setScript(string $script): bool 39 | { 40 | $dist = (string) file_get_contents($this->distFilePath); 41 | 42 | if ( ! $dist) { 43 | return false; 44 | } 45 | 46 | $dist = str_replace('{{X_SCRIPT}}', $script, $dist); 47 | 48 | return (bool) file_put_contents($this->distFilePath, $dist); 49 | } 50 | 51 | private function die(string $msg, bool $die = true): void 52 | { 53 | $msg = "[ScriptGeneration] {$msg}\n"; 54 | 55 | if ($die) { 56 | exit($msg); 57 | } 58 | 59 | echo $msg; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Components/Ping/components/store.ts: -------------------------------------------------------------------------------- 1 | import { configure, makeAutoObservable } from 'mobx'; 2 | import type { 3 | ServerToBrowserPingItemProps, 4 | ServerToBrowserPingProps, 5 | } from '../typings.ts';configure({ 6 | enforceActions: 'observed', 7 | }); 8 | class Main { 9 | isPing = false; 10 | isPingServerToBrowser = false; 11 | isPingServerToServer = false; 12 | serverToBrowserPingItems: ServerToBrowserPingItemProps[] = []; 13 | serverToServerPingItems: ServerToBrowserPingProps[] = []; 14 | constructor() { 15 | makeAutoObservable(this); 16 | } 17 | setIsPing = (isPing: boolean) => { 18 | this.isPing = isPing; 19 | }; 20 | setIsPingServerToBrowser = (isPingServerToBrowser: boolean) => { 21 | this.isPingServerToBrowser = isPingServerToBrowser; 22 | }; 23 | setIsPingServerToServer = (isPingServerToServer: boolean) => { 24 | this.isPingServerToServer = isPingServerToServer; 25 | }; 26 | setServerToBrowserPingItems = ( 27 | serverToBrowserPingItems: ServerToBrowserPingItemProps[] 28 | ) => { 29 | this.serverToBrowserPingItems = serverToBrowserPingItems; 30 | }; 31 | setServerToServerPingItems = ( 32 | serverToServerPingItems: ServerToBrowserPingProps[] 33 | ) => { 34 | this.serverToServerPingItems = serverToServerPingItems; 35 | }; 36 | addServerToBrowserPingItem = ( 37 | serverToBrowserPingItem: ServerToBrowserPingItemProps 38 | ) => { 39 | this.serverToBrowserPingItems.push(serverToBrowserPingItem); 40 | }; 41 | addServerToServerPingItem = ( 42 | serverToServerPingItem: ServerToBrowserPingProps 43 | ) => { 44 | this.serverToServerPingItems.push(serverToServerPingItem); 45 | }; 46 | } 47 | export const PingStore = new Main(); 48 | -------------------------------------------------------------------------------- /src/Components/Location/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite'; 2 | import { type FC, type MouseEvent, useCallback, useState } from 'react'; 3 | import { Button } from '@/Components/Button/components/index.tsx'; 4 | import { ButtonStatus } from '@/Components/Button/components/typings.ts'; 5 | import { serverFetch } from '@/Components/Fetch/server-fetch.ts'; 6 | import { gettext } from '@/Components/Language/index.ts'; 7 | import { OK } from '@/Components/Rest/http-status.ts'; 8 | import { ToastStore } from '@/Components/Toast/components/store.ts'; 9 | import type { LocationProps } from './typings.ts'; 10 | export const Location: FC<{ 11 | ip: string; 12 | }> = observer(({ ip }) => { 13 | const [loading, setLoading] = useState(false); 14 | const [location, setLocation] = useState(null); 15 | const onClick = useCallback( 16 | async (e: MouseEvent) => { 17 | e.preventDefault(); 18 | e.stopPropagation(); 19 | if (loading) { 20 | return; 21 | } 22 | setLoading(true); 23 | const { data, status } = await serverFetch( 24 | `locationIpv4&ip=${ip}` 25 | ); 26 | setLoading(false); 27 | if (data && status === OK) { 28 | setLocation(data); 29 | return; 30 | } 31 | ToastStore.open(gettext('Can not fetch location.')); 32 | }, 33 | [ip, loading] 34 | ); 35 | return ( 36 | 44 | ); 45 | }); 46 | -------------------------------------------------------------------------------- /src/Components/BrowserBenchmark/components/browsers-item.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-server-benchmark-bg: transparent; 3 | --x-server-benchmark-link-bg: hsl(0 0% 0% / 0.05); 4 | --x-server-benchmark-link-fg: hsl(0 0% 0% / 0.95); 5 | @media (prefers-color-scheme: dark) { 6 | // --x-server-benchmark-bg: hsl(0 0% 100% / 0.05); 7 | --x-server-benchmark-link-fg: hsl(0 0% 100% / 0.95); 8 | --x-server-benchmark-link-bg: hsl(0 0% 100% / 0.05); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | // justify-content: center; 14 | gap: var(--x-gutter-sm); 15 | border-radius: var(--x-radius); 16 | background: var(--x-server-benchmark-bg); 17 | padding: var(--x-gutter-sm); 18 | text-align: center; 19 | } 20 | .header { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | .link { 26 | opacity: 0.75; 27 | cursor: pointer; 28 | border: none; 29 | border-radius: var(--x-radius); 30 | background: none; 31 | padding: 0 var(--x-gutter-sm); 32 | &:hover, 33 | &:active { 34 | opacity: 1; 35 | background: var(--x-server-benchmark-link-bg); 36 | text-decoration: none; 37 | } 38 | svg { 39 | width: 1rem; 40 | height: 1rem; 41 | } 42 | } 43 | .marks { 44 | display: flex; 45 | justify-content: center; 46 | align-items: center; 47 | gap: var(--x-gutter-sm); 48 | cursor: pointer; 49 | border: none; 50 | border-radius: var(--x-radius); 51 | background: transparent; 52 | color: var(--x-server-benchmark-link-fg); 53 | font-size: 1.25rem; 54 | // font-family: Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif; 55 | &:hover { 56 | background: var(--x-server-benchmark-link-bg); 57 | } 58 | } 59 | .sign { 60 | opacity: 0.5; 61 | } 62 | -------------------------------------------------------------------------------- /src/Components/ServerBenchmark/components/server-item.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-server-benchmark-bg: transparent; 3 | --x-server-benchmark-link-bg: hsl(0 0% 0% / 0.05); 4 | --x-server-benchmark-link-fg: hsl(0 0% 0% / 0.95); 5 | @media (prefers-color-scheme: dark) { 6 | // --x-server-benchmark-bg: hsl(0 0% 100% / 0.05); 7 | --x-server-benchmark-link-fg: hsl(0 0% 100% / 0.95); 8 | --x-server-benchmark-link-bg: hsl(0 0% 100% / 0.05); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | // justify-content: center; 14 | gap: var(--x-gutter-sm); 15 | border-radius: var(--x-radius); 16 | background: var(--x-server-benchmark-bg); 17 | padding: var(--x-gutter-sm); 18 | text-align: center; 19 | } 20 | .header { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | .link { 26 | opacity: 0.75; 27 | cursor: pointer; 28 | border: none; 29 | border-radius: var(--x-radius); 30 | background: none; 31 | padding: 0 var(--x-gutter-sm); 32 | &:hover, 33 | &:active { 34 | opacity: 1; 35 | background: var(--x-server-benchmark-link-bg); 36 | text-decoration: none; 37 | } 38 | svg { 39 | width: 1rem; 40 | height: 1rem; 41 | } 42 | } 43 | .marks { 44 | display: flex; 45 | justify-content: center; 46 | align-items: center; 47 | gap: var(--x-gutter-sm); 48 | cursor: pointer; 49 | border: none; 50 | border-radius: var(--x-radius); 51 | background-color: transparent; 52 | color: var(--x-server-benchmark-link-fg); 53 | font-size: 1.25rem; 54 | // font-family: Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif; 55 | &:hover { 56 | background: var(--x-server-benchmark-link-bg); 57 | } 58 | } 59 | .sign { 60 | opacity: 0.5; 61 | } 62 | -------------------------------------------------------------------------------- /tools/lang-builder.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @version 1.0.0 3 | */ 4 | 5 | import { writeFileSync } from 'node:fs'; 6 | import path, { basename, dirname } from 'node:path'; 7 | import { fileURLToPath } from 'node:url'; 8 | import DeepSort from 'deep-sort-object'; 9 | import FastGlob from 'fast-glob'; 10 | import { PoParser } from './po-parser.mjs'; 11 | import { PotBuilder } from './pot-builder.mjs'; 12 | 13 | const __dirname = dirname(fileURLToPath(import.meta.url)); 14 | class LangBuilder { 15 | data = {}; 16 | constructor() { 17 | this.build(); 18 | } 19 | setData = async () => { 20 | new PotBuilder({ 21 | potPath: path.resolve(__dirname, '../locales/lang.pot'), 22 | sourceDir: path.resolve(__dirname, '../src'), 23 | }); 24 | const files = await FastGlob(path.resolve(__dirname, '../locales/*.po')); 25 | for (const file of files) { 26 | const lang = basename(file, '.po'); 27 | const langId = lang.replace('_', '').toLowerCase(); 28 | const parser = new PoParser({ 29 | poPath: path.resolve(__dirname, `../locales/${lang}.po`), 30 | }); 31 | const items = parser.parse(); 32 | for (const text of Object.keys(items)) { 33 | if (!this.data[text]) { 34 | this.data[text] = {}; 35 | } 36 | if (!this.data[text][langId]) { 37 | this.data[text][langId] = items[text]; 38 | } 39 | } 40 | } 41 | }; 42 | build = async () => { 43 | await this.setData(); 44 | this.data = DeepSort(this.data); 45 | const jsonPath = path.resolve( 46 | __dirname, 47 | '../src/Components/Language/data.json' 48 | ); 49 | writeFileSync(jsonPath, JSON.stringify(this.data, null, 2)); 50 | }; 51 | } 52 | new LangBuilder(); 53 | -------------------------------------------------------------------------------- /src/Components/NetworkStats/components/item.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-network-stats-tx-fg: hsl(23 100% 38%); 3 | --x-network-stats-tx-bg: hsl(23 100% 38% / 0.1); 4 | --x-network-stats-rx-fg: hsl(120 100% 23%); 5 | --x-network-stats-rx-bg: hsl(120 100% 23% / 0.1); 6 | 7 | @media (prefers-color-scheme: dark) { 8 | --x-network-stats-tx-fg: hsl(23 100% 58%); 9 | --x-network-stats-tx-bg: hsl(23 100% 58% /0.15); 10 | --x-network-stats-rx-fg: hsl(120 100% 43%); 11 | --x-network-stats-rx-bg: hsl(120 100% 43% / 0.15); 12 | } 13 | } 14 | .main { 15 | display: grid; 16 | grid-template-areas: 17 | "network-stats-item-id network-stats-item-id" 18 | "network-stats-item-rx network-stats-item-tx"; 19 | gap: 1px; 20 | font-family: "Arial Black", sans-serif; 21 | } 22 | .id { 23 | grid-area: network-stats-item-id; 24 | text-align: center; 25 | } 26 | .type { 27 | &::before { 28 | opacity: 0.5; 29 | content: "▼"; 30 | font-size: 1rem; 31 | } 32 | } 33 | .rx, 34 | .tx { 35 | display: grid; 36 | position: relative; 37 | grid-area: network-stats-item-rx; 38 | border-radius: var(--x-radius) 0 0 var(--x-radius); 39 | background: var(--x-network-stats-rx-bg); 40 | padding: var(--x-gutter-sm); 41 | color: var(--x-network-stats-rx-fg); 42 | text-align: center; 43 | } 44 | .tx { 45 | grid-area: network-stats-item-tx; 46 | border-radius: 0 var(--x-radius) var(--x-radius) 0; 47 | background: var(--x-network-stats-tx-bg); 48 | color: var(--x-network-stats-tx-fg); 49 | .type { 50 | &::before { 51 | content: "▲"; 52 | } 53 | } 54 | } 55 | .totalRx, 56 | .rateRx, 57 | .totalTx, 58 | .rateTx { 59 | } 60 | .rateRx, 61 | .rateTx { 62 | font-weight: bold; 63 | font-size: 1.5rem; 64 | } 65 | -------------------------------------------------------------------------------- /src/Components/ServerStatus/components/system-load.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --x-sys-load-fg: var(--x-fg); 3 | --x-sys-load-bg: transparent; 4 | --x-sys-load-interval-bg: hsl(0 0% 0% / 0.1); 5 | @media (prefers-color-scheme: dark) { 6 | --x-sys-load-fg: var(--x-fg); 7 | // --x-sys-load-bg: hsl(0 0% 100% / 0.05); 8 | --x-sys-load-interval-bg: hsl(0 0% 100% / 0.1); 9 | } 10 | } 11 | .main { 12 | display: grid; 13 | grid-template-columns: 1fr auto; 14 | grid-template-areas: 15 | "x-server-stats-system-load-label x-server-stats-system-load-usage" 16 | "x-server-stats-system-load-label x-server-stats-system-load-group" 17 | "x-server-stats-system-load-meter x-server-stats-system-load-meter"; 18 | // align-items: center; 19 | gap: var(--x-gutter-sm); 20 | // border-radius: var(--x-radius); 21 | // background: var(--x-sys-load-bg); 22 | // padding: var(--x-gutter-sm); 23 | } 24 | .label { 25 | display: grid; 26 | grid-area: x-server-stats-system-load-label; 27 | align-items: center; 28 | font-weight: bold; 29 | } 30 | .meter { 31 | display: grid; 32 | grid-template-areas: "x-meter-core"; 33 | grid-area: x-server-stats-system-load-meter; 34 | } 35 | .usage { 36 | grid-area: x-server-stats-system-load-usage; 37 | // font-size: 1.25rem; 38 | // font-weight: bold; 39 | text-align: right; 40 | } 41 | .group { 42 | display: flex; 43 | grid-area: x-server-stats-system-load-group; 44 | align-items: center; 45 | gap: var(--x-gutter-sm); 46 | } 47 | .groupItem { 48 | border-radius: var(--x-radius); 49 | background: var(--x-sys-load-interval-bg); 50 | padding: 0 var(--x-gutter); 51 | color: var(--x-sys-load-fg); 52 | font-weight: bold; 53 | font-family: "Arial Black", sans-serif, monospace; 54 | } 55 | -------------------------------------------------------------------------------- /src/Components/Poll/components/typings.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigProps } from '@/Components/Config/typings.ts'; 2 | import type { DatabasePollDataProps } from '@/Components/Database/components/typings.ts'; 3 | import type { DiskUsagePollDataProps } from '@/Components/DiskUsage/components/typings.ts'; 4 | import type { MyInfoPollDataProps } from '@/Components/MyInfo/components/typings.ts'; 5 | import type { NetworkStatsPollDataProps } from '@/Components/NetworkStats/components/typings.ts'; 6 | import type { NodesPollDataProps } from '@/Components/Nodes/components/typings.ts'; 7 | import type { PhpExtensionsPollDataProps } from '@/Components/PhpExtensions/components/typings.ts'; 8 | import type { PhpInfoPollDataProps } from '@/Components/PhpInfo/components/typings.ts'; 9 | import type { ServerInfoPollDataProps } from '@/Components/ServerInfo/components/typings.ts'; 10 | import type { ServerStatusPollDataProps } from '@/Components/ServerStatus/components/typings.ts'; 11 | import type { TemperatureSensorPollDataProps } from '@/Components/TemperatureSensor/components/typings.ts'; 12 | import type { UserConfigProps } from '@/Components/UserConfig/typings.ts'; 13 | 14 | export interface PollDataProps { 15 | config: ConfigProps | null; 16 | userConfig: UserConfigProps | null; 17 | database: DatabasePollDataProps | null; 18 | myInfo: MyInfoPollDataProps | null; 19 | phpInfo: PhpInfoPollDataProps | null; 20 | diskUsage: DiskUsagePollDataProps | null; 21 | networkStats: NetworkStatsPollDataProps | null; 22 | phpExtensions: PhpExtensionsPollDataProps | null; 23 | serverStatus: ServerStatusPollDataProps | null; 24 | serverInfo: ServerInfoPollDataProps | null; 25 | nodes: NodesPollDataProps | null; 26 | temperatureSensor: TemperatureSensorPollDataProps | null; 27 | } 28 | --------------------------------------------------------------------------------