Skip to content

Commit abd18a6

Browse files
committed
feat: frontend bug fixes
1 parent 5517b91 commit abd18a6

File tree

1 file changed

+157
-98
lines changed

1 file changed

+157
-98
lines changed

src/pages/create/compute.jsx

+157-98
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default function DockerContainers() {
6060
const [isTestingDeploy, setIsTestingDeploy] = useState(false); // placeholder for isTestingDeploy state
6161
const [configureModalOpen, setConfigureModalOpen] = useState(false);
6262
const [selectedContainer, setSelectedContainer] = useState(null);
63+
const [hasDeployedThisSession, setHasDeployedThisSession] = useState(false);
6364

6465
// --- IMAGE SEARCH, FILTER, PAGINATION STATE ---
6566
const [imageSearch, setImageSearch] = useState('');
@@ -222,6 +223,12 @@ export default function DockerContainers() {
222223

223224
const handleSubmit = async (e) => {
224225
e.preventDefault();
226+
// Enforce frontend container limit
227+
const MAX_CONTAINERS = 50;
228+
if (containers.length >= MAX_CONTAINERS) {
229+
toast.error(`You have reached the maximum of ${MAX_CONTAINERS} containers. Reach out to [email protected] if you need more.`);
230+
return;
231+
}
225232
try {
226233
const userId = localStorage.getItem('username');
227234

@@ -670,7 +677,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
670677
return (
671678
<div className="mt-4 shadow-xl bg-neutral-900/50 p-4 shadow-lg">
672679
<div className="flex justify-between items-center mb-2">
673-
<h4 className="text-lg font-bold text-white flex items-center gap-2">
680+
<h4 className="text-lg font-bold text-white mb-6 flex items-center gap-2">
674681
<i className="fas fa-folder-open text-blue-400"></i>
675682
Associated Files
676683
</h4>
@@ -766,7 +773,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
766773
};
767774

768775
const handleStartDeploy = async () => {
769-
setIsTestingDeploy(true);
776+
setIsTestingDeploy(false);
770777
setBuildLogs([]);
771778
try {
772779
// Collect relevant data for test deploy
@@ -792,12 +799,29 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
792799
const reader = response.body.getReader();
793800
let decoder = new TextDecoder('utf-8');
794801
let done = false;
802+
let foundDomain = false;
795803
while (!done) {
796804
const { value, done: doneReading } = await reader.read();
797805
done = doneReading;
798806
if (value) {
799807
const chunk = decoder.decode(value);
800-
setBuildLogs(prev => [...prev, ...chunk.split(/\r?\n/).filter(line => line.trim())]);
808+
const lines = chunk.split(/\r?\n/).filter(line => line.trim());
809+
setBuildLogs(prev => [...prev, ...lines]);
810+
if (!foundDomain) {
811+
for (const line of lines) {
812+
// Look for [DOMAIN] https://... or discordapp.com in the line
813+
let match = line.match(/\[DOMAIN\]\s*(https?:\/\/\S+)/);
814+
if (match) {
815+
foundDomain = true;
816+
break;
817+
}
818+
match = line.match(/https?:\/\/[\w.-]*discordapp\.com\S*/);
819+
if (match) {
820+
foundDomain = true;
821+
break;
822+
}
823+
}
824+
}
801825
}
802826
}
803827
} catch (error) {
@@ -841,6 +865,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
841865
useEffect(() => {
842866
if (createContainerModalOpen) {
843867
document.body.style.overflow = 'hidden';
868+
setHasDeployedThisSession(false);
844869
} else {
845870
document.body.style.overflow = '';
846871
}
@@ -887,6 +912,17 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
887912
e.stopPropagation();
888913
};
889914

915+
// --- Deployment UI logic based on logs ---
916+
const isDeploySuccess = buildLogs.some(line => line.includes("Discordbot/2.0; +https://discordapp.com"));
917+
const deployedDomain = (() => {
918+
let d = "";
919+
buildLogs.forEach(line => {
920+
const m = line.match(/\[DOMAIN\]\s*(https?:\/\/\S+)/);
921+
if (m) d = m[1];
922+
});
923+
return d;
924+
})();
925+
890926
return (
891927
<>
892928
<Head>
@@ -1072,7 +1108,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
10721108
<button
10731109
type="button"
10741110
onClick={createDockerfileFromTemplate}
1075-
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-none text-sm transition-all duration-200"
1111+
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-none transition-all duration-200"
10761112
>
10771113
Use This Template
10781114
</button>
@@ -1234,7 +1270,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
12341270
return (
12351271
<div
12361272
key={uniqueKey}
1237-
className={`bg-neutral-850 hover:bg-neutral-800 border-blue-400 shadow-lg p-5 transition-all hover:shadow-xl cursor-pointer ${expandedImageId === expandedKey ? 'border-t-4 border-blue-500 bg-neutral-800' : ''}`}
1273+
className={`bg-neutral-700/20 hover:bg-neutral-800 shadow-lg p-5 transition-all hover:shadow-xl cursor-pointer ${expandedImageId === expandedKey ? 'border-t-4 border-blue-500 bg-neutral-800' : ''}`}
12381274
onClick={() => setExpandedImageId(expandedImageId === expandedKey ? null : expandedKey)}
12391275
>
12401276
<div className="flex flex-col gap-2">
@@ -1338,7 +1374,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
13381374
/>
13391375
<button
13401376
type="button"
1341-
className="ml-2 px-2 py-1 bg-blue-700 text-white rounded text-xs hover:bg-blue-800 transition"
1377+
className="ml-2 px-2 py-1 bg-blue-700 text-white rounded hover:bg-blue-800 transition"
13421378
onClick={() => setFormData(f => ({ ...f, name: generateCoolName() }))}
13431379
title="Generate random name"
13441380
>
@@ -1593,22 +1629,22 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
15931629

15941630
{/* Deploy Container Modal */}
15951631
{createContainerModalOpen && (
1596-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60">
1597-
<div className="bg-neutral-900 rounded-lg shadow-xl p-8 w-full max-w-7xl relative grid grid-cols-6">
1632+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 mx-auto ">
1633+
<div className="bg-neutral-900 rounded-lg shadow-xl px-8 pb-4 w-full max-w-7xl relative grid grid-cols-6 items-center">
15981634
<div className="col-span-3">
15991635
<button
16001636
className="absolute top-3 right-3 text-gray-400 hover:text-red-400"
16011637
onClick={() => setCreateContainerModalOpen(false)}
16021638
>
16031639
<XCircleIcon className="w-6 h-6" />
16041640
</button>
1605-
<h2 className="text-2xl font-bold text-white mb-4 flex items-center">
1641+
<h2 className="text-2xl font-bold text-white mb-4 flex items-center pb-2">
16061642
<ServerIcon className="w-6 h-6 mr-2 text-blue-500" />
16071643
Deploy Container
16081644
</h2>
16091645
<div>
16101646
<div className="mb-4">
1611-
<label className="block text-gray-300 text-sm mb-1">Container Name</label>
1647+
<label className="block text-gray-300 text-sm mb-1">Container Name <button type="button" className=" ml-1 w-1 hover:text-blue-500 text-white rounded text-xs transition" onClick={() => setFormData(f => ({ ...f, name: generateCoolName() }))} title="Generate random name"><i className="fa fa-sync"></i></button></label>
16121648
<input
16131649
type="text"
16141650
name="name"
@@ -1659,7 +1695,7 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
16591695
</div>
16601696
</div>
16611697
<div className="mb-4">
1662-
<label className="block text-gray-300 text-sm mb-1">Bind to Challenge</label>
1698+
<label className="block text-gray-300 text-sm mb-1">Bind to Challenge (Optional)</label>
16631699
<select
16641700
value={challengeId}
16651701
onChange={e => setChallengeId(e.target.value)}
@@ -1687,103 +1723,126 @@ CMD ["socat", "TCP-LISTEN:9999,fork,reuseaddr", "EXEC:/challenge/challenge"]`
16871723
Test Deployment
16881724
</button>
16891725
<button
1690-
className="flex-1 py-2 px-4 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-none shadow-md transition-all"
1691-
onClick={handleStartDeploy}
1692-
disabled={isLoading}
1726+
className={`flex-1 py-2 px-4 bg-green-700 text-white font-semibold rounded-none shadow-md transition-all ${hasDeployedThisSession ? 'bg-green-900 cursor-not-allowed ' : 'hover:bg-green-800'} flex items-center justify-center gap-2`}
1727+
onClick={() => {
1728+
setHasDeployedThisSession(true);
1729+
handleStartDeploy();
1730+
}}
1731+
disabled={isLoading || hasDeployedThisSession}
16931732
>
1694-
{isLoading ? 'Deploying...' : 'Deploy Container'}
1733+
{isLoading && (
1734+
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
1735+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
1736+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
1737+
</svg>
1738+
)}
1739+
{isLoading ? 'Deploying...' : hasDeployedThisSession ? <i className="fas fa-check"></i> : 'Deploy Container'}
16951740
</button>
16961741
</div>
16971742
</div>
16981743
</div>
1699-
<div id="testdeploy" className="col-span-3 ml-4 mt-4 h-full w-full">
1700-
<div className="w-full h-full">
17011744

1702-
<div className="bg-black/80 h-[400px] text-white font-mono text-xs rounded p-4 overflow-y-auto border border-neutral-800" id="build-log-stream">
1703-
{/* Build logs will be streamed here */}
1704-
<p>Build logs will show here...</p>
1705-
<br></br>
1706-
<p className="text-red-400">This test deployment will be active for 5 minutes.</p>
1707-
<br></br>
1708-
<p>Your deployment will not automatically end, once you see expected behavior click Deploy.</p>
1709-
<br></br>
1710-
{buildLogs.map((line, idx) => {
1711-
// Remove leading "data: " if present
1712-
const cleanLine = line.replace(/^data: ?/, "");
1713-
// Regex to match URLs
1714-
const urlRegex = /(https?:\/\/[^\s]+)/g;
1715-
// Split the line by URLs, keeping the URLs
1716-
const parts = cleanLine.split(urlRegex);
1717-
return (
1718-
<div key={idx}>
1719-
{parts.map((part, i) =>
1720-
urlRegex.test(part) ? (
1721-
<a
1722-
key={i}
1723-
href={part}
1724-
target="_blank"
1725-
rel="noopener noreferrer"
1726-
className="underline text-blue-400 hover:text-blue-300 break-all"
1727-
>
1728-
{part}
1729-
</a>
1730-
) : (
1731-
<span key={i}>{part}</span>
1732-
)
1733-
)}
1734-
</div>
1735-
);
1736-
})}
1737-
1745+
1746+
<div id="deploysuccess" className="col-span-3 ml-4 mt-2 justify-center items-center">
1747+
{isDeploySuccess ? (
1748+
<div className="flex flex-col items-center justify-center h-full px-10">
1749+
1750+
<div className="mb-4 w-[300px] h-[250px] rounded-xl">
1751+
<ComposableMap
1752+
projection="geoAlbersUsa"
1753+
width={300}
1754+
height={250}
1755+
projectionConfig={{ center: [0, 0], scale: 400, rotation: 0, }}
1756+
style={{ background: 'transparent' }}
1757+
>
1758+
<Geographies geography="https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json">
1759+
{({ geographies }) =>
1760+
geographies.map(geo => (
1761+
<Geography
1762+
key={geo.rsmKey}
1763+
geography={geo}
1764+
fill="#23272f"
1765+
stroke="#334155"
1766+
style={{ outline: 'none', filter: 'drop-shadow(0 2px 8px #0ea5e955)' }}
1767+
/>
1768+
))
1769+
}
1770+
</Geographies>
1771+
{/* State College, PA marker with pulse */}
1772+
<Marker coordinates={[-77.8600, 40.7934]}>
1773+
<circle r={8} fill="#34a3ff" stroke="#34a3ff" strokeWidth={0.05} style={{ filter: 'drop-shadow(0 0 10px #4adeffb3)' }} />
1774+
</Marker> </ComposableMap>
1775+
</div>
1776+
{/* Region label */}
1777+
<div className="text-blue-300 font-semibold text-sm mb-4">
1778+
US East (State College, PA)
1779+
</div>
1780+
{/* Domain box */}
1781+
<div className="bg-neutral-800 rounded-lg p-4 w-full max-w-md text-center shadow border border-neutral-700 mb-4">
1782+
<div className="text-gray-400 text-xs mb-1">Autogenerated Domain</div>
1783+
<div className="text-md font-mono text-blue-400 break-all select-all">{deployedDomain}</div>
1784+
</div>
1785+
{/* Description */}
1786+
<div className="text-gray-400 text-xs mt-2 text-center max-w-xs">
1787+
{isTestingDeploy
1788+
? "This is a test deployment. It will automatically expire in 5 minutes. Use this environment for testing only."
1789+
: "Your container will be on our servers in State College, PA (US East). "}
17381790
</div>
1739-
17401791
</div>
1792+
) : (
1793+
<div className="bg-black/80 h-[400px] text-white font-mono text-xs rounded p-4 overflow-y-auto border border-neutral-800" id="build-log-stream">
1794+
{/* Build logs will be streamed here */}
1795+
<p>Build logs will show here...</p>
1796+
<br></br>
1797+
{isTestingDeploy && (
1798+
<>
1799+
<p className="text-red-400">This test deployment will be active for 5 minutes.</p>
1800+
<br></br>
1801+
</>
1802+
)}
17411803

1742-
</div>
1743-
1744-
<div id="deploysuccess" className="col-span-3 hidden ">
1745-
<div className="flex flex-col items-center justify-center h-full px-10">
1746-
{/* Sexy Interactive Map with react-simple-maps */}
1747-
<div className="mb-4 w-[300px] h-[25s0px] rounded-xl">
1748-
<ComposableMap
1749-
projection="geoAlbersUsa"
1750-
width={300}
1751-
height={250}
1752-
projectionConfig={{ center: [0, 0], scale: 400, rotation: 0, }}
1753-
style={{ background: 'transparent' }}
1754-
>
1755-
<Geographies geography="https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json">
1756-
{({ geographies }) =>
1757-
geographies.map(geo => (
1758-
<Geography
1759-
key={geo.rsmKey}
1760-
geography={geo}
1761-
fill="#23272f"
1762-
stroke="#334155"
1763-
style={{ outline: 'none', filter: 'drop-shadow(0 2px 8px #0ea5e955)' }}
1764-
/>
1765-
))
1766-
}
1767-
</Geographies>
1768-
{/* State College, PA marker with pulse */}
1769-
<Marker coordinates={[-77.8600, 40.7934]}>
1770-
<circle r={8} fill="#34a3ff" stroke="#34a3ff" strokeWidth={0.05} style={{ filter: 'drop-shadow(0 0 10px #4adeffb3)' }} />
1771-
</Marker> </ComposableMap>
1772-
</div>
1773-
{/* Region label */}
1774-
<div className="text-blue-300 font-semibold text-sm mb-4">
1775-
US East (State College, PA)
1776-
</div>
1777-
{/* Domain box */}
1778-
<div className="bg-neutral-800 rounded-lg p-4 w-full max-w-md text-center shadow border border-neutral-700 mb-4">
1779-
<div className="text-gray-400 text-xs mb-1">Autogenerated Domain</div>
1780-
<div className="text-md font-mono text-blue-400 break-all select-all">laphatize333330ujasd.ctfgui.de</div>
1781-
</div>
1782-
{/* Description */}
1783-
<div className="text-gray-400 text-xs mt-2 text-center max-w-xs">
1784-
Your container will be on our servers in State College, PA (US East). Domain is autogenerated and will be visible after deployment.
1804+
<pre className="text-yellow-400 text-xs">
1805+
{`
1806+
dP\"\"b8 888888 888888 dP\"\"b8 88 88 88 8888b. 888888
1807+
dP \"\" 88 88__ dP \"\" 88 88 88 8I Yb 88__
1808+
Yb 88 88\"" Yb \"88 Y8 8P 88 8I dY 88\""
1809+
YboodP 88 88 YboodP \`YbodP\' 88 8888Y" 888888
1810+
`}
1811+
</pre>
1812+
<br></br>
1813+
<p>Note, sometimes we can't automatically detect when a container is ready. You can verify if your container is accessible by clicking on the generated domain.</p>
1814+
<br></br>
1815+
<p className="text-cyan-400">Log streaming is highly experimental and may not work for all containers.</p>
1816+
<br></br>
1817+
{buildLogs.map((line, idx) => {
1818+
// Remove leading "data: " if present
1819+
const cleanLine = line.replace(/^data: ?/, "");
1820+
// Regex to match URLs
1821+
const urlRegex = /(https?:\/\/[^\s]+)/g;
1822+
// Split the line by URLs, keeping the URLs
1823+
const parts = cleanLine.split(urlRegex);
1824+
return (
1825+
<div key={idx}>
1826+
{parts.map((part, i) =>
1827+
urlRegex.test(part) ? (
1828+
<a
1829+
key={i}
1830+
href={part}
1831+
target="_blank"
1832+
rel="noopener noreferrer"
1833+
className="underline text-blue-400 hover:text-blue-300 break-all"
1834+
>
1835+
{part}
1836+
</a>
1837+
) : (
1838+
<span key={i}>{part}</span>
1839+
)
1840+
)}
1841+
</div>
1842+
);
1843+
})}
17851844
</div>
1786-
</div>
1845+
)}
17871846
</div>
17881847
</div>
17891848
</div>

0 commit comments

Comments
 (0)