ui updates
This commit is contained in:
43
static/frontend/src/Api.jsx
Normal file
43
static/frontend/src/Api.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = "http://192.168.2.151:8000"
|
||||
|
||||
|
||||
const createSha256Hash = async (input) => {
|
||||
try {
|
||||
// Convert string to array buffer
|
||||
const msgBuffer = new TextEncoder().encode(input);
|
||||
|
||||
// Generate hash
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
|
||||
// Convert buffer to hex string
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return hashHex;
|
||||
} catch (error) {
|
||||
console.error('Error creating SHA-256 hash:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const sendRequest = async (action,args) => {
|
||||
try {
|
||||
const TS = Math.round(Date.now()/1000);
|
||||
const hash = await createSha256Hash("<password>"+JSON.stringify(args)+TS);
|
||||
const response = await axios.post(API_URL+"/action/"+action, {args:args,hash:hash+"."+TS}, {
|
||||
timeout: 4000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error in POST request:', error);
|
||||
throw error.response?.data || error.message;
|
||||
}
|
||||
};
|
||||
|
||||
export default sendRequest;
|
||||
@@ -13,6 +13,27 @@
|
||||
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
|
||||
}
|
||||
|
||||
@keyframes flipRight {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.glass:hover>.absolute>img {
|
||||
animation: flipRight 0.5s forwards;
|
||||
animation-delay: 0s;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.glass_enabled {
|
||||
background: rgba(30, 240, 135, 0.2);
|
||||
border-color: rgba(51, 234, 127, 0.7);
|
||||
@@ -42,7 +63,7 @@
|
||||
|
||||
.gauge span {
|
||||
color: #d8b4fe;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -1,226 +1,266 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import './App.css'
|
||||
import sendRequest from "./Api.jsx";
|
||||
|
||||
|
||||
window.sendRequest = sendRequest;
|
||||
const TextTile = ({ label }) => (
|
||||
<div className="tile-content">
|
||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||
</div>
|
||||
<div className="tile-content">
|
||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
|
||||
<div className="tile-content cursor-pointer" onClick={toggleEnabled}>
|
||||
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||
</div>
|
||||
<div className="tile-content cursor-pointer">
|
||||
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
||||
<span className="text-purple-400 text-lg font-semibold mt-2">{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ClockTile = ({ id }) => {
|
||||
const [time, setTime] = useState("");
|
||||
const [date, setDate] = useState("");
|
||||
const [time, setTime] = useState("");
|
||||
const [date, setDate] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const updateClock = () => {
|
||||
const now = new Date();
|
||||
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
||||
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
||||
const date = now.toLocaleDateString('ru-RU', options)
|
||||
.replace(/,/, '')
|
||||
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
||||
.replace(/г\./, '');
|
||||
setTime(time);
|
||||
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
||||
};
|
||||
updateClock();
|
||||
const interval = setInterval(updateClock, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const updateClock = () => {
|
||||
const now = new Date();
|
||||
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
||||
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
||||
const date = now.toLocaleDateString('ru-RU', options)
|
||||
.replace(/,/, '')
|
||||
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
||||
.replace(/г\./, '');
|
||||
setTime(time);
|
||||
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
||||
};
|
||||
updateClock();
|
||||
const interval = setInterval(updateClock, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="tile-content">
|
||||
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
||||
<span className="text-purple-400 text-lg font-semibold">{date}</span>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="tile-content">
|
||||
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
||||
<span className="text-purple-400 text-lg font-semibold mt-2">{date}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GaugeTile = ({ gauges }) => (
|
||||
<div className="tile-content gauge-tile-content">
|
||||
<div className="flex flex-col gap-4">
|
||||
{gauges.map((g, index) => (
|
||||
<div key={index} className="flex items-center gap-4">
|
||||
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
||||
<span>{Math.round((g.value / 360) * 100)}%</span>
|
||||
</div>
|
||||
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tile-content gauge-tile-content">
|
||||
<div className="flex flex-col gap-4">
|
||||
{gauges.map((g, index) => (
|
||||
<div key={index} className="flex items-center gap-4">
|
||||
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
||||
<span className='font-bold'>{Math.round((g.value / 360) * 100)}%</span>
|
||||
</div>
|
||||
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
|
||||
return (
|
||||
<div className="tile-content">
|
||||
<span className="text-purple-400 text-lg font-semibold">Slider Controls</span>
|
||||
<div className="flex flex-col w-full gap-4">
|
||||
{sliders.map((s, index) => (
|
||||
<div key={index} className="flex flex-col items-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
||||
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={s.value}
|
||||
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => {
|
||||
return (
|
||||
<div className="tile-content">
|
||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||
<div className="flex flex-col w-full gap-4">
|
||||
{sliders.map((s, index) => (
|
||||
<div key={index} className="flex flex-col items-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
||||
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={s.value}
|
||||
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NumberTile = ({ value, label }) => (
|
||||
<div className="tile-content">
|
||||
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||
</div>
|
||||
<div className="tile-content">
|
||||
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
||||
<span className="text-purple-400 text-lg font-semibold mt-2">{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const [tiles, setTiles] = useState([
|
||||
{ id: 1, type: "text", label: "Tile 1" },
|
||||
{ id: 2, type: "gauge", gauges: [{ value: 75, label: "Metric 1" }, { value: 120, label: "Metric 2" }] },
|
||||
{ id: 3, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 1", enabled: true },
|
||||
{ id: 4, type: "break" },
|
||||
{ id: 5, type: "text", label: "Tile 4" },
|
||||
{ id: 6, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 2", enabled: true },
|
||||
{ id: 7, type: "clock" },
|
||||
{ id: 8, type: "slider", sliders: [{ value: 50, label: "Slider 1" }, { value: 75, label: "Slider 2" }] },
|
||||
{ id: 9, type: "text", label: "Tile 5 (Double)" },
|
||||
{ id: 10, type: "text", label: "Tile 6" },
|
||||
{ id: 11, type: "number", value: 42, label: "Data Point" }
|
||||
]);
|
||||
const [tiles, setTiles] = useState([
|
||||
{ id: 0, type: "break", label: "Система" },
|
||||
|
||||
const updateGaugeValue = (tileId, gaugeIndex) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "gauge") {
|
||||
const newGauges = [...tile.gauges];
|
||||
newGauges[gaugeIndex] = {
|
||||
...newGauges[gaugeIndex],
|
||||
value: (newGauges[gaugeIndex].value + 10) % 360
|
||||
};
|
||||
return { ...tile, gauges: newGauges };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
{ id: 1, type: "clock", mini_icon:"/icons/mini/clock-five.png" },
|
||||
|
||||
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "slider") {
|
||||
const newSliders = [...tile.sliders];
|
||||
newSliders[sliderIndex] = {
|
||||
...newSliders[sliderIndex],
|
||||
value: parseInt(newValue)
|
||||
};
|
||||
return { ...tile, sliders: newSliders };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
{ id: 2, type: "gauge", gauges: [{ value: 3.60 * 0, label: "Загрузка CPU" }, { value: 3.60 * 0, label: "Занято RAM" }], tag: "system_load", mini_icon:"/icons/mini/chart-line-up.png" },
|
||||
|
||||
const updateNumberValue = (tileId) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "number") {
|
||||
return { ...tile, value: tile.value + 1 };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
{ id: 3, type: "number", value: 0, label: "Трафик Mbit/s", tag: "net_traffic", mini_icon:"/icons/mini/wifi.png" },
|
||||
{ id: 4, type: "slider", label: "Настройки", sliders: [{ value: 50, label: "Яроксть экрана", action: "screen.chenge_brightness" }, { value: 75, label: "Громкость звука", action: "media.set_volume" }], mini_icon:"/icons/mini/settings.png" },
|
||||
|
||||
const toggleImageEnabled = (tileId) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "image") {
|
||||
return { ...tile, enabled: !tile.enabled };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
{ id: 5, type: "break", label: "Сайты" },
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{tiles.map(tile => (
|
||||
<div key={tile.id}>
|
||||
{tile.type === "break" ? (
|
||||
<div className="w-full"></div>
|
||||
) : (
|
||||
<div
|
||||
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-96 h-48" : "w-48 h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
||||
>
|
||||
{tile.type === "text" && <TextTile label={tile.label} />}
|
||||
{tile.type === "image" && (
|
||||
<ImageTile
|
||||
imageUrl={tile.imageUrl}
|
||||
label={tile.label}
|
||||
enabled={tile.enabled}
|
||||
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
||||
/>
|
||||
)}
|
||||
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
||||
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
||||
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
||||
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
||||
{(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && (
|
||||
<div className="absolute top-2 right-2 flex gap-2">
|
||||
{tile.type === "gauge" && tile.gauges.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => updateGaugeValue(tile.id, index)}
|
||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||
>
|
||||
Update Gauge {index + 1}
|
||||
</button>
|
||||
))}
|
||||
{tile.type === "slider" && tile.sliders.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
|
||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||
>
|
||||
Increment Slider {index + 1}
|
||||
</button>
|
||||
))}
|
||||
{tile.type === "number" && (
|
||||
<button
|
||||
onClick={() => updateNumberValue(tile.id)}
|
||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||
>
|
||||
Increment Number
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{ id: 6, type: "image", imageUrl: "/icons/steam-svgrepo-com.svg", label: "Steam", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||
{ id: 7, type: "image", imageUrl: "/icons/telegram-logo-svgrepo-com.svg", label: "Telegram", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||
{ id: 8, type: "image", imageUrl: "/icons/cloud.png", label: "Cloud", action: "web.open_url", action_args: { url: "https://cloud.viadev.su" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||
|
||||
{ id: 9, type: "break", label: "Управление ПК" },
|
||||
{ id: 10, type: "image", imageUrl: "/icons/vpn.png", label: "VPN", action: "v2ray.enable_vpn", action_args: {} },
|
||||
]);
|
||||
|
||||
|
||||
|
||||
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "slider") {
|
||||
const newSliders = [...tile.sliders];
|
||||
newSliders[sliderIndex] = {
|
||||
...newSliders[sliderIndex],
|
||||
value: parseInt(newValue)
|
||||
};
|
||||
return { ...tile, sliders: newSliders };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const toggleImageEnabled = (tileId) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId && tile.type === "image") {
|
||||
return { ...tile, enabled: !tile.enabled };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
const setTileProps = (tileId, props) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.id === tileId) {
|
||||
return { ...tile, ...props };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
const setTilePropsByTag = (tag, props) => {
|
||||
setTiles(prevTiles =>
|
||||
prevTiles.map(tile => {
|
||||
if (tile.tag === tag) {
|
||||
return { ...tile, ...props };
|
||||
}
|
||||
return tile;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateSystemStats = async () => {
|
||||
try {
|
||||
const data = await sendRequest("system.get_system_metrics", {});
|
||||
setTilePropsByTag("system_load", {
|
||||
gauges: [
|
||||
{ value: (3.60 * data.result.metrics.cpu_usage_percent).toFixed(0), label: "Загрузка CPU" },
|
||||
{ value: (3.60 * (data.result.metrics.ram_used_gb / data.result.metrics.ram_total_gb) * 100).toFixed(0), label: "Занято RAM" }
|
||||
]
|
||||
})
|
||||
setTilePropsByTag("net_traffic", {
|
||||
value: data.result.metrics.network_traffic_mbps
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
updateSystemStats();
|
||||
const interval = setInterval(updateSystemStats, 2000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
|
||||
const onTileClick = async (tile) => {
|
||||
console.log("click", tile)
|
||||
|
||||
if (tile.action) {
|
||||
try {
|
||||
setTileProps(tile.id, { loading: true })
|
||||
await sendRequest(tile.action, tile.action_args);
|
||||
|
||||
if (tile.action.includes("enable")) {
|
||||
tile.enabled = true;
|
||||
tile.action = tile.action.replace("enable", "disable");
|
||||
} else if (tile.action.includes("disable")) {
|
||||
tile.enabled = false;
|
||||
tile.action = tile.action.replace("disable", "enable");
|
||||
}
|
||||
tile.loading = false;
|
||||
|
||||
setTileProps(tile.id, tile)
|
||||
} catch (e) {
|
||||
setTileProps(tile.id, { loading: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-6 flex-wrap gap-4">
|
||||
{tiles.map(tile => (
|
||||
<div key={tile.id} className={tile.type === "break" ? "col-span-full" : (tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" || tile.type === "number" ? "w-full sm:w-auto col-span-2" : "col-span-1")}>
|
||||
{tile.type === "break" ? (
|
||||
<div className="w-full mt-2 text-purple-400 text-2xl font-bold">{tile.label}</div>
|
||||
) : (
|
||||
<div
|
||||
onClick={() => { onTileClick(tile) }}
|
||||
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-full h-45 lg:h-48" : "w-full h-45 lg:h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
||||
>
|
||||
{tile.loading &&
|
||||
<div className={"flex animate-[fadeIn_0.5s_ease-out] absolute inset-0 bg-black/10 backdrop-blur-[3px] flex-col items-center justify-center z-10 rounded-xl"}>
|
||||
<div className="w-10 h-10 border-4 border-purple-500 border-t-transparent rounded-full animate-spin" style={{ animationDuration: "0.5s" }} ></div>
|
||||
</div>
|
||||
}
|
||||
{!tile.loading &&
|
||||
<>
|
||||
{tile.type === "text" && <TextTile label={tile.label} />}
|
||||
{tile.type === "image" && (
|
||||
<ImageTile
|
||||
imageUrl={tile.imageUrl}
|
||||
label={tile.label}
|
||||
enabled={tile.enabled}
|
||||
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
||||
/>
|
||||
)}
|
||||
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
||||
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
||||
{tile.type === "slider" && <SliderTile label={tile.label} sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
||||
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
||||
{tile.mini_icon && (
|
||||
<div className="absolute top-3 right-3 flex gap-2">
|
||||
|
||||
<img src={tile.mini_icon} className='h-5 w-5'/>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default App
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss";
|
||||
#root{
|
||||
width:100%;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<body className='bg-gray-900 min-h-screen p-4'>
|
||||
<body className='bg-gray-900 min-h-screen py-4 flex justify-center px-3 2xl:px-[15%]'>
|
||||
<App />
|
||||
</body>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user