mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: docker usage examples & statfs
This commit is contained in:
115
apps/client/app/components/bytes-size.tsx
Normal file
115
apps/client/app/components/bytes-size.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
|
||||
type ByteSizeProps = {
|
||||
bytes: number;
|
||||
base?: 1000 | 1024; // 1000 = SI (KB, MB, ...), 1024 = IEC (KiB, MiB, ...)
|
||||
maximumFractionDigits?: number; // default: 2
|
||||
smartRounding?: boolean; // dynamically reduces decimals for big numbers (default: true)
|
||||
locale?: string | string[]; // e.g., 'en', 'de', or navigator.languages
|
||||
space?: boolean; // space between number and unit (default: true)
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
fallback?: string; // shown if bytes is not a finite number (default: '—')
|
||||
};
|
||||
|
||||
const SI_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] as const;
|
||||
const IEC_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] as const;
|
||||
|
||||
type FormatBytesResult = {
|
||||
text: string;
|
||||
unit: string;
|
||||
unitIndex: number;
|
||||
numeric: number; // numeric value before formatting (with sign)
|
||||
};
|
||||
|
||||
export function formatBytes(
|
||||
bytes: number,
|
||||
options?: {
|
||||
base?: 1000 | 1024;
|
||||
maximumFractionDigits?: number;
|
||||
smartRounding?: boolean;
|
||||
locale?: string | string[];
|
||||
},
|
||||
): FormatBytesResult {
|
||||
const { base = 1000, maximumFractionDigits = 2, smartRounding = true, locale } = options ?? {};
|
||||
|
||||
if (!Number.isFinite(bytes)) {
|
||||
return {
|
||||
text: "—",
|
||||
unit: "",
|
||||
unitIndex: 0,
|
||||
numeric: NaN,
|
||||
};
|
||||
}
|
||||
|
||||
const units = base === 1024 ? IEC_UNITS : SI_UNITS;
|
||||
|
||||
const sign = Math.sign(bytes) || 1;
|
||||
const abs = Math.abs(bytes);
|
||||
|
||||
let idx = 0;
|
||||
if (abs > 0) {
|
||||
idx = Math.floor(Math.log(abs) / Math.log(base));
|
||||
if (!Number.isFinite(idx)) idx = 0;
|
||||
idx = Math.max(0, Math.min(idx, units.length - 1));
|
||||
}
|
||||
|
||||
const numeric = (abs / Math.pow(base, idx)) * sign;
|
||||
|
||||
const maxFrac = (() => {
|
||||
if (!smartRounding) return maximumFractionDigits;
|
||||
const v = Math.abs(numeric);
|
||||
if (v >= 100) return 0;
|
||||
if (v >= 10) return Math.min(1, maximumFractionDigits);
|
||||
return maximumFractionDigits;
|
||||
})();
|
||||
|
||||
const text = new Intl.NumberFormat(locale, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: maxFrac,
|
||||
}).format(numeric);
|
||||
|
||||
return {
|
||||
text,
|
||||
unit: units[idx],
|
||||
unitIndex: idx,
|
||||
numeric,
|
||||
};
|
||||
}
|
||||
|
||||
export function ByteSize(props: ByteSizeProps) {
|
||||
const {
|
||||
bytes,
|
||||
base = 1000,
|
||||
maximumFractionDigits = 2,
|
||||
smartRounding = true,
|
||||
locale,
|
||||
space = true,
|
||||
className,
|
||||
style,
|
||||
fallback = "—",
|
||||
} = props;
|
||||
|
||||
const { text, unit } = formatBytes(bytes, {
|
||||
base,
|
||||
maximumFractionDigits,
|
||||
smartRounding,
|
||||
locale,
|
||||
});
|
||||
|
||||
if (text === "—") {
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{fallback}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{text}
|
||||
{space ? " " : ""}
|
||||
{unit}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
46
apps/client/app/components/ui/code-block.tsx
Normal file
46
apps/client/app/components/ui/code-block.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism-twilight.css";
|
||||
import "prismjs/components/prism-yaml";
|
||||
import { toast } from "sonner";
|
||||
import { copyToClipboard } from "~/utils/clipboard";
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
language?: string;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export const CodeBlock: React.FC<CodeBlockProps> = ({ code, language = "jsx", filename }) => {
|
||||
useEffect(() => {
|
||||
Prism.highlightAll();
|
||||
}, []);
|
||||
|
||||
const handleCopy = async () => {
|
||||
await copyToClipboard(code);
|
||||
toast.success("Code copied to clipboard");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-sm bg-slate-900 ring-1 ring-white/10">
|
||||
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2 text-xs text-slate-400">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-rose-500" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-amber-500" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-emerald-500" />
|
||||
{filename && <span className="ml-3 font-medium text-slate-300">{filename}</span>}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCopy()}
|
||||
className="cursor-pointer rounded-md bg-white/5 px-2 py-1 text-[11px] font-medium text-slate-300 ring-1 ring-inset ring-white/10 transition hover:bg-white/10 active:translate-y-px"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre className="overflow-x-auto leading-6 text-xs m-0" style={{ marginTop: 0, marginBottom: 0 }}>
|
||||
<code className={`language-${language}`}>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user