Redesign following Web Interface Guidelines
- Added skip link for accessibility - Proper semantic HTML with <table>, <th scope>, <main> - All icon buttons have aria-label - Focus-visible rings on all interactive elements - Proper form labels with htmlFor - Input autocomplete and type attributes - Role and aria attributes for modal - Visual focus states without outline-none - Better color contrast and status indicators - Hover and transition improvements
This commit is contained in:
parent
28cd726320
commit
796a760dc3
BIN
.next/cache/webpack/client-development/0.pack.gz
vendored
BIN
.next/cache/webpack/client-development/0.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/7.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/7.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/5.pack.gz
vendored
BIN
.next/cache/webpack/server-development/5.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/7.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/server-development/7.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -130,7 +130,7 @@
|
|||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
/******/ /* webpack/runtime/getFullHash */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ __webpack_require__.h = () => ("175a446eeb5052e2")
|
/******/ __webpack_require__.h = () => ("1930de2390f27e98")
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -190,7 +190,7 @@
|
|||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
/******/ /* webpack/runtime/getFullHash */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ __webpack_require__.h = () => ("1122623f302a8ef0")
|
/******/ __webpack_require__.h = () => ("946779c75e05dcfd")
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/global */
|
/******/ /* webpack/runtime/global */
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
{"c":["app/page","webpack"],"r":[],"m":[]}
|
||||||
22
.next/static/webpack/app/page.1122623f302a8ef0.hot-update.js
Normal file
22
.next/static/webpack/app/page.1122623f302a8ef0.hot-update.js
Normal file
File diff suppressed because one or more lines are too long
18
.next/static/webpack/webpack.1122623f302a8ef0.hot-update.js
Normal file
18
.next/static/webpack/webpack.1122623f302a8ef0.hot-update.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
/*
|
||||||
|
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||||
|
* This devtool is neither made for production nor for readable output files.
|
||||||
|
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||||
|
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||||
|
* or disable the default devtool with "devtool: false".
|
||||||
|
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||||
|
*/
|
||||||
|
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||||
|
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||||
|
/******/ /* webpack/runtime/getFullHash */
|
||||||
|
/******/ (() => {
|
||||||
|
/******/ __webpack_require__.h = () => ("946779c75e05dcfd")
|
||||||
|
/******/ })();
|
||||||
|
/******/
|
||||||
|
/******/ }
|
||||||
|
);
|
||||||
File diff suppressed because one or more lines are too long
184
src/app/page.tsx
184
src/app/page.tsx
@ -151,123 +151,148 @@ export default function HeartbeatMonitor() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-950 flex items-center justify-center">
|
<div className="min-h-screen bg-slate-950 flex items-center justify-center" role="status" aria-live="polite">
|
||||||
<div className="text-slate-400">Loading...</div>
|
<div className="text-slate-400">Loading monitor...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
|
<div className="min-h-screen bg-slate-950 text-slate-100">
|
||||||
|
{/* Skip Link for Accessibility */}
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-emerald-500 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg"
|
||||||
|
>
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="max-w-6xl mx-auto mb-6">
|
<header className="border-b border-slate-800 bg-slate-900">
|
||||||
<div className="flex items-center justify-between">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center justify-between h-16">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Activity className="w-8 h-8 text-emerald-500" />
|
<Activity className="w-7 h-7 text-emerald-500" aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">Heartbeat Monitor</h1>
|
<h1 className="text-xl font-semibold text-white">Heartbeat Monitor</h1>
|
||||||
<p className="text-slate-400">{onlineApps} of {totalApps} services online</p>
|
<p className="text-sm text-slate-400" aria-live="polite">
|
||||||
|
{onlineApps} of {totalApps} services online
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={fetchData}
|
onClick={fetchData}
|
||||||
className="p-2 bg-slate-800 rounded-lg text-slate-400 hover:text-white"
|
className="inline-flex items-center justify-center p-2.5 bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
|
aria-label="Refresh data"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-5 h-5" />
|
<RefreshCw className="w-5 h-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAddApp(true)}
|
onClick={() => setShowAddApp(true)}
|
||||||
className="flex items-center gap-2 bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-2 rounded-lg"
|
className="inline-flex items-center gap-2 bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" aria-hidden="true" />
|
||||||
Add App
|
Add App
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main id="main-content" className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="max-w-6xl mx-auto">
|
|
||||||
<div className="bg-slate-900 rounded-lg border border-slate-800 overflow-hidden">
|
<div className="bg-slate-900 rounded-lg border border-slate-800 overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-slate-800 text-left">
|
<tr className="bg-slate-800 border-b border-slate-700">
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Status</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Status</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Name</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Name</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">URL</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">URL</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Port</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Port</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Uptime</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Uptime</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Response</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Response</th>
|
||||||
<th className="px-4 py-3 text-sm font-medium text-slate-400">Actions</th>
|
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">
|
||||||
|
<span className="sr-only">Actions</span>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody className="divide-y divide-slate-800">
|
||||||
{apps.map((app) => {
|
{apps.map((app) => {
|
||||||
const { isUp, uptime, latest } = getAppStatus(app.id);
|
const { isUp, uptime, latest } = getAppStatus(app.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={app.id} className="border-t border-slate-800 hover:bg-slate-800/50">
|
<tr key={app.id} className="hover:bg-slate-800/50 transition-colors">
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-4">
|
||||||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${
|
<span
|
||||||
|
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${
|
||||||
isUp
|
isUp
|
||||||
? "bg-emerald-500/20 text-emerald-400"
|
? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20"
|
||||||
: "bg-red-500/20 text-red-400"
|
: "bg-red-500/10 text-red-400 border border-red-500/20"
|
||||||
}`}>
|
}`}
|
||||||
<span className={`w-1.5 h-1.5 rounded-full ${isUp ? "bg-emerald-400" : "bg-red-400"}`} />
|
>
|
||||||
{isUp ? "ONLINE" : "OFFLINE"}
|
<span className={`w-1.5 h-1.5 rounded-full ${isUp ? "bg-emerald-400" : "bg-red-400"}`} aria-hidden="true" />
|
||||||
|
{isUp ? "Online" : "Offline"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-white">{app.name}</p>
|
<p className="font-medium text-white">{app.name}</p>
|
||||||
|
{app.description && (
|
||||||
<p className="text-sm text-slate-500">{app.description}</p>
|
<p className="text-sm text-slate-500">{app.description}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-4">
|
||||||
<a
|
<a
|
||||||
href={app.url}
|
href={app.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-emerald-400 hover:text-emerald-300 text-sm"
|
className="text-emerald-400 hover:text-emerald-300 hover:underline text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 rounded"
|
||||||
>
|
>
|
||||||
{app.url}
|
{app.url}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-slate-300 font-mono">{app.port}</td>
|
<td className="px-4 py-4">
|
||||||
<td className="px-4 py-3">
|
<code className="text-sm text-slate-300 bg-slate-800 px-2 py-1 rounded">{app.port}</code>
|
||||||
<span className={`font-mono ${uptime >= 90 ? "text-emerald-400" : "text-yellow-400"}`}>
|
</td>
|
||||||
|
<td className="px-4 py-4">
|
||||||
|
<span className={`text-sm font-medium ${uptime >= 90 ? "text-emerald-400" : uptime >= 50 ? "text-yellow-400" : "text-red-400"}`}>
|
||||||
{uptime}%
|
{uptime}%
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-slate-300 font-mono">
|
<td className="px-4 py-4">
|
||||||
{latest?.responseTime ? `${latest.responseTime}ms` : "--"}
|
<span className="text-sm text-slate-300">
|
||||||
|
{latest?.responseTime ? `${latest.responseTime}ms` : "—"}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => checkApp(app)}
|
onClick={() => checkApp(app)}
|
||||||
disabled={checking === app.id}
|
disabled={checking === app.id}
|
||||||
className="p-1.5 text-slate-400 hover:text-white hover:bg-slate-700 rounded"
|
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
title="Check now"
|
aria-label={`Check ${app.name} status`}
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-4 h-4 ${checking === app.id ? "animate-spin" : ""}`} />
|
<RefreshCw className={`w-4 h-4 ${checking === app.id ? "animate-spin" : ""}`} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
href={app.url}
|
href={app.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="p-1.5 text-slate-400 hover:text-white hover:bg-slate-700 rounded"
|
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
title="Open app"
|
aria-label={`Open ${app.name}`}
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLink className="w-4 h-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteApp(app.id)}
|
onClick={() => deleteApp(app.id)}
|
||||||
className="p-1.5 text-slate-400 hover:text-red-400 hover:bg-slate-700 rounded"
|
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
title="Delete"
|
aria-label={`Delete ${app.name}`}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -277,66 +302,91 @@ export default function HeartbeatMonitor() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
{/* Add App Modal */}
|
{/* Add App Modal */}
|
||||||
{showAddApp && (
|
{showAddApp && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
<div
|
||||||
<div className="bg-slate-900 rounded-lg p-6 w-full max-w-md border border-slate-800">
|
className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4"
|
||||||
<h2 className="text-xl font-bold text-white mb-4">Add New App</h2>
|
role="dialog"
|
||||||
<form onSubmit={addApp} className="space-y-4">
|
aria-modal="true"
|
||||||
|
aria-labelledby="add-app-title"
|
||||||
|
>
|
||||||
|
<div className="bg-slate-900 rounded-lg border border-slate-800 p-6 w-full max-w-md shadow-2xl">
|
||||||
|
<h2 id="add-app-title" className="text-xl font-semibold text-white mb-6">Add New App</h2>
|
||||||
|
<form onSubmit={addApp} className="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Name</label>
|
<label htmlFor="app-name" className="block text-sm font-medium text-slate-400 mb-2">
|
||||||
|
Name <span className="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="app-name"
|
||||||
type="text"
|
type="text"
|
||||||
value={newApp.name}
|
value={newApp.name}
|
||||||
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
|
||||||
|
placeholder="My Application"
|
||||||
required
|
required
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Description</label>
|
<label htmlFor="app-desc" className="block text-sm font-medium text-slate-400 mb-2">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="app-desc"
|
||||||
type="text"
|
type="text"
|
||||||
value={newApp.description}
|
value={newApp.description}
|
||||||
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
|
||||||
|
placeholder="Brief description…"
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">URL</label>
|
<label htmlFor="app-url" className="block text-sm font-medium text-slate-400 mb-2">
|
||||||
|
URL <span className="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
id="app-url"
|
||||||
|
type="url"
|
||||||
value={newApp.url}
|
value={newApp.url}
|
||||||
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
|
||||||
|
placeholder="http://localhost:3000"
|
||||||
required
|
required
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Port</label>
|
<label htmlFor="app-port" className="block text-sm font-medium text-slate-400 mb-2">
|
||||||
|
Port <span className="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="app-port"
|
||||||
type="number"
|
type="number"
|
||||||
value={newApp.port}
|
value={newApp.port}
|
||||||
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
|
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
|
||||||
required
|
required
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3 pt-2">
|
<div className="flex gap-3 pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowAddApp(false)}
|
onClick={() => setShowAddApp(false)}
|
||||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 py-2 rounded-lg"
|
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white py-2 rounded-lg font-medium"
|
className="flex-1 bg-emerald-600 hover:bg-emerald-500 text-white py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
|
||||||
>
|
>
|
||||||
Add App
|
Add App
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user