fix(frontend): A form label must be associated with an input

This commit is contained in:
tigattack
2025-09-24 23:38:06 +01:00
parent a84da7c731
commit 74f42b5bee
9 changed files with 349 additions and 82 deletions

View File

@@ -31,7 +31,7 @@ import {
Wifi,
X,
} from "lucide-react";
import React, { useState } from "react";
import React, { useId, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import InlineEdit from "../components/InlineEdit";
import {
@@ -1001,6 +1001,8 @@ const HostDetail = () => {
const CredentialsModal = ({ host, isOpen, onClose }) => {
const [showApiKey, setShowApiKey] = useState(false);
const [activeTab, setActiveTab] = useState("quick-install");
const apiIdInputId = useId();
const apiKeyInputId = useId();
const { data: serverUrlData } = useQuery({
queryKey: ["serverUrl"],
@@ -1342,11 +1344,15 @@ echo " - View logs: tail -f /var/log/patchmon-agent.log"`;
</h4>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={apiIdInputId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
API ID
</label>
<div className="flex items-center gap-2">
<input
id={apiIdInputId}
type="text"
value={host.api_id}
readOnly
@@ -1364,11 +1370,15 @@ echo " - View logs: tail -f /var/log/patchmon-agent.log"`;
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={apiKeyInputId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
API Key
</label>
<div className="flex items-center gap-2">
<input
id={apiKeyInputId}
type={showApiKey ? "text" : "password"}
value={host.api_key}
readOnly

View File

@@ -8,7 +8,7 @@ import {
Trash2,
Users,
} from "lucide-react";
import React, { useState } from "react";
import React, { useId, useState } from "react";
import { hostGroupsAPI } from "../utils/api";
const HostGroups = () => {
@@ -252,6 +252,9 @@ const HostGroups = () => {
// Create Host Group Modal
const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
const nameId = useId();
const descriptionId = useId();
const colorId = useId();
const [formData, setFormData] = useState({
name: "",
description: "",
@@ -279,11 +282,15 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={nameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Name *
</label>
<input
type="text"
id={nameId}
name="name"
value={formData.name}
onChange={handleChange}
@@ -294,10 +301,14 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={descriptionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Description
</label>
<textarea
id={descriptionId}
name="description"
value={formData.description}
onChange={handleChange}
@@ -308,12 +319,16 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={colorId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Color
</label>
<div className="flex items-center gap-3">
<input
type="color"
id={colorId}
name="color"
value={formData.color}
onChange={handleChange}
@@ -350,6 +365,9 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
// Edit Host Group Modal
const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
const editNameId = useId();
const editDescriptionId = useId();
const editColorId = useId();
const [formData, setFormData] = useState({
name: group.name,
description: group.description || "",
@@ -377,11 +395,15 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Name *
</label>
<input
type="text"
id={editNameId}
name="name"
value={formData.name}
onChange={handleChange}
@@ -392,10 +414,14 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editDescriptionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Description
</label>
<textarea
id={editDescriptionId}
name="description"
value={formData.description}
onChange={handleChange}
@@ -406,12 +432,16 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editColorId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Color
</label>
<div className="flex items-center gap-3">
<input
type="color"
id={editColorId}
name="color"
value={formData.color}
onChange={handleChange}

View File

@@ -28,7 +28,7 @@ import {
Users,
X,
} from "lucide-react";
import React, { useEffect, useState } from "react";
import React, { useEffect, useId, useState } from "react";
import { Link, useNavigate, useSearchParams } from "react-router-dom";
import InlineEdit from "../components/InlineEdit";
import InlineGroupEdit from "../components/InlineGroupEdit";
@@ -43,6 +43,8 @@ import { OSIcon } from "../utils/osIcons.jsx";
// Add Host Modal Component
const AddHostModal = ({ isOpen, onClose, onSuccess }) => {
const friendlyNameId = useId();
const hostGroupId = useId();
const [formData, setFormData] = useState({
friendly_name: "",
hostGroupId: "",
@@ -113,11 +115,15 @@ const AddHostModal = ({ isOpen, onClose, onSuccess }) => {
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={friendlyNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Friendly Name *
</label>
<input
type="text"
id={friendlyNameId}
required
value={formData.friendly_name}
onChange={(e) =>
@@ -133,9 +139,9 @@ const AddHostModal = ({ isOpen, onClose, onSuccess }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-3">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-3">
Host Group
</label>
</span>
<div className="grid grid-cols-3 gap-2">
{/* No Group Option */}
<button
@@ -231,6 +237,8 @@ const AddHostModal = ({ isOpen, onClose, onSuccess }) => {
// Credentials Modal Component
const CredentialsModal = ({ host, isOpen, onClose }) => {
const apiIdId = useId();
const apiKeyId = useId();
const [showApiKey, setShowApiKey] = useState(false);
const [activeTab, setActiveTab] = useState(
host?.isNewHost ? "quick" : "credentials",
@@ -508,12 +516,16 @@ echo " - View logs: tail -f /var/log/patchmon-agent.log"`,
{activeTab === "credentials" && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<label
htmlFor={apiIdId}
className="block text-sm font-medium text-secondary-700 mb-2"
>
API ID
</label>
<div className="flex">
<input
type="text"
id={apiIdId}
readOnly
value={host.apiId}
className="flex-1 block w-full border-secondary-300 rounded-l-md shadow-sm bg-secondary-50"
@@ -529,12 +541,16 @@ echo " - View logs: tail -f /var/log/patchmon-agent.log"`,
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<label
htmlFor={apiKeyId}
className="block text-sm font-medium text-secondary-700 mb-2"
>
API Key
</label>
<div className="flex">
<input
type={showApiKey ? "text" : "password"}
id={apiKeyId}
readOnly
value={host.apiKey}
className="flex-1 block w-full border-secondary-300 rounded-l-md shadow-sm bg-secondary-50"
@@ -779,6 +795,10 @@ echo " - View logs: tail -f /var/log/patchmon-agent.log"`,
};
const Hosts = () => {
const hostGroupFilterId = useId();
const statusFilterId = useId();
const osFilterId = useId();
const bulkHostGroupId = useId();
const [showAddModal, setShowAddModal] = useState(false);
const [selectedHosts, setSelectedHosts] = useState([]);
const [showBulkAssignModal, setShowBulkAssignModal] = useState(false);
@@ -1737,10 +1757,14 @@ const Hosts = () => {
<div className="bg-secondary-50 dark:bg-secondary-700 p-4 rounded-lg border dark:border-secondary-600">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={hostGroupFilterId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Host Group
</label>
<select
id={hostGroupFilterId}
value={groupFilter}
onChange={(e) => setGroupFilter(e.target.value)}
className="w-full border border-secondary-300 dark:border-secondary-600 rounded-lg px-3 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-800 text-secondary-900 dark:text-white"
@@ -1755,10 +1779,14 @@ const Hosts = () => {
</select>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={statusFilterId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Status
</label>
<select
id={statusFilterId}
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="w-full border border-secondary-300 dark:border-secondary-600 rounded-lg px-3 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-800 text-secondary-900 dark:text-white"
@@ -1771,10 +1799,14 @@ const Hosts = () => {
</select>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={osFilterId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Operating System
</label>
<select
id={osFilterId}
value={osFilter}
onChange={(e) => setOsFilter(e.target.value)}
className="w-full border border-secondary-300 dark:border-secondary-600 rounded-lg px-3 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-800 text-secondary-900 dark:text-white"
@@ -2116,10 +2148,14 @@ const BulkAssignModal = ({
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">
<label
htmlFor={bulkHostGroupId}
className="block text-sm font-medium text-secondary-700 mb-1"
>
Host Group
</label>
<select
id={bulkHostGroupId}
value={selectedGroupId}
onChange={(e) => setSelectedGroupId(e.target.value)}
className="w-full px-3 py-2 border border-secondary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
@@ -2309,7 +2345,7 @@ const ColumnSettingsModal = ({
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, index)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
// Focus handling for keyboard users
}

View File

@@ -9,7 +9,7 @@ import {
Trash2,
Users,
} from "lucide-react";
import React, { useState } from "react";
import React, { useId, useState } from "react";
import { hostGroupsAPI } from "../utils/api";
const Options = () => {
@@ -332,6 +332,9 @@ const Options = () => {
// Create Host Group Modal
const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
const nameId = useId();
const descriptionId = useId();
const colorId = useId();
const [formData, setFormData] = useState({
name: "",
description: "",
@@ -359,11 +362,15 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={nameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Name *
</label>
<input
type="text"
id={nameId}
name="name"
value={formData.name}
onChange={handleChange}
@@ -374,10 +381,14 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={descriptionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Description
</label>
<textarea
id={descriptionId}
name="description"
value={formData.description}
onChange={handleChange}
@@ -388,12 +399,16 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={colorId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Color
</label>
<div className="flex items-center gap-3">
<input
type="color"
id={colorId}
name="color"
value={formData.color}
onChange={handleChange}
@@ -430,6 +445,9 @@ const CreateHostGroupModal = ({ onClose, onSubmit, isLoading }) => {
// Edit Host Group Modal
const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
const editNameId = useId();
const editDescriptionId = useId();
const editColorId = useId();
const [formData, setFormData] = useState({
name: group.name,
description: group.description || "",
@@ -457,11 +475,15 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Name *
</label>
<input
type="text"
id={editNameId}
name="name"
value={formData.name}
onChange={handleChange}
@@ -472,10 +494,14 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editDescriptionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Description
</label>
<textarea
id={editDescriptionId}
name="description"
value={formData.description}
onChange={handleChange}
@@ -486,12 +512,16 @@ const EditHostGroupModal = ({ group, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editColorId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Color
</label>
<div className="flex items-center gap-3">
<input
type="color"
id={editColorId}
name="color"
value={formData.color}
onChange={handleChange}

View File

@@ -16,7 +16,7 @@ import {
Users,
X,
} from "lucide-react";
import React, { useEffect, useState } from "react";
import React, { useEffect, useId, useState } from "react";
import { useAuth } from "../contexts/AuthContext";
import { permissionsAPI } from "../utils/api";
@@ -320,6 +320,7 @@ const RolePermissionsCard = ({
<div key={field.key} className="flex items-start">
<div className="flex items-center h-5">
<input
id={`${role.role}-${field.key}`}
type="checkbox"
checked={isChecked}
onChange={(e) =>
@@ -335,7 +336,10 @@ const RolePermissionsCard = ({
<div className="ml-3">
<div className="flex items-center">
<Icon className="h-4 w-4 text-secondary-400 mr-2" />
<label className="text-sm font-medium text-secondary-900 dark:text-white">
<label
htmlFor={`${role.role}-${field.key}`}
className="text-sm font-medium text-secondary-900 dark:text-white"
>
{field.label}
</label>
</div>
@@ -354,6 +358,7 @@ const RolePermissionsCard = ({
// Add Role Modal Component
const AddRoleModal = ({ isOpen, onClose, onSuccess }) => {
const roleNameInputId = useId();
const [formData, setFormData] = useState({
role: "",
can_view_dashboard: true,
@@ -404,10 +409,14 @@ const AddRoleModal = ({ isOpen, onClose, onSuccess }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={roleNameInputId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Role Name
</label>
<input
id={roleNameInputId}
type="text"
name="role"
required
@@ -439,13 +448,17 @@ const AddRoleModal = ({ isOpen, onClose, onSuccess }) => {
].map((permission) => (
<div key={permission.key} className="flex items-center">
<input
id={`add-role-${permission.key}`}
type="checkbox"
name={permission.key}
checked={formData[permission.key]}
onChange={handleInputChange}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 rounded"
/>
<label className="ml-2 block text-sm text-secondary-700 dark:text-secondary-200">
<label
htmlFor={`add-role-${permission.key}`}
className="ml-2 block text-sm text-secondary-700 dark:text-secondary-200"
>
{permission.label}
</label>
</div>

View File

@@ -34,6 +34,8 @@ const Profile = () => {
const currentPasswordId = useId();
const newPasswordId = useId();
const confirmPasswordId = useId();
const verificationTokenId = useId();
const disablePasswordId = useId();
const { user, updateProfile, changePassword } = useAuth();
const { theme, toggleTheme, isDark } = useTheme();
const [activeTab, setActiveTab] = useState("profile");
@@ -924,10 +926,14 @@ const TfaTab = () => {
</p>
<form onSubmit={handleVerify} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={verificationTokenId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Verification Code
</label>
<input
id={verificationTokenId}
type="text"
value={verificationToken}
onChange={(e) =>
@@ -1030,10 +1036,14 @@ const TfaTab = () => {
</p>
<form onSubmit={handleDisable} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={disablePasswordId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Password
</label>
<input
id={disablePasswordId}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}

View File

@@ -21,6 +21,9 @@ import { repositoryAPI } from "../utils/api";
const RepositoryDetail = () => {
const isActiveId = useId();
const repositoryNameId = useId();
const priorityId = useId();
const descriptionId = useId();
const { repositoryId } = useParams();
const queryClient = useQueryClient();
const [editMode, setEditMode] = useState(false);
@@ -201,11 +204,15 @@ const RepositoryDetail = () => {
{editMode ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
<label
htmlFor={repositoryNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1"
>
Repository Name
</label>
<input
type="text"
id={repositoryNameId}
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
@@ -214,11 +221,15 @@ const RepositoryDetail = () => {
/>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
<label
htmlFor={priorityId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1"
>
Priority
</label>
<input
type="number"
id={priorityId}
value={formData.priority}
onChange={(e) =>
setFormData({ ...formData, priority: e.target.value })
@@ -228,10 +239,14 @@ const RepositoryDetail = () => {
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
<label
htmlFor={descriptionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1"
>
Description
</label>
<textarea
id={descriptionId}
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
@@ -263,9 +278,9 @@ const RepositoryDetail = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
URL
</label>
</span>
<div className="flex items-center mt-1">
<Globe className="h-4 w-4 text-secondary-400 mr-2" />
<span className="text-secondary-900 dark:text-white">
@@ -274,25 +289,25 @@ const RepositoryDetail = () => {
</div>
</div>
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Distribution
</label>
</span>
<p className="text-secondary-900 dark:text-white mt-1">
{repository.distribution}
</p>
</div>
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Components
</label>
</span>
<p className="text-secondary-900 dark:text-white mt-1">
{repository.components}
</p>
</div>
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Repository Type
</label>
</span>
<p className="text-secondary-900 dark:text-white mt-1">
{repository.repoType}
</p>
@@ -300,9 +315,9 @@ const RepositoryDetail = () => {
</div>
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Security
</label>
</span>
<div className="flex items-center mt-1">
{repository.isSecure ? (
<>
@@ -319,9 +334,9 @@ const RepositoryDetail = () => {
</div>
{repository.priority && (
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Priority
</label>
</span>
<p className="text-secondary-900 dark:text-white mt-1">
{repository.priority}
</p>
@@ -329,18 +344,18 @@ const RepositoryDetail = () => {
)}
{repository.description && (
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Description
</label>
</span>
<p className="text-secondary-900 dark:text-white mt-1">
{repository.description}
</p>
</div>
)}
<div>
<label className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
<span className="text-sm font-medium text-secondary-500 dark:text-secondary-400">
Created
</label>
</span>
<div className="flex items-center mt-1">
<Calendar className="h-4 w-4 text-secondary-400 mr-2" />
<span className="text-secondary-900 dark:text-white">

View File

@@ -30,6 +30,17 @@ const Settings = () => {
const repoPrivateId = useId();
const useCustomSshKeyId = useId();
const isDefaultId = useId();
const protocolId = useId();
const hostId = useId();
const portId = useId();
const updateIntervalId = useId();
const defaultRoleId = useId();
const repositoryTypeId = useId();
const githubRepoUrlId = useId();
const sshKeyPathId = useId();
const versionId = useId();
const releaseNotesId = useId();
const scriptContentId = useId();
const [formData, setFormData] = useState({
serverProtocol: "http",
serverHost: "localhost",
@@ -470,10 +481,14 @@ const Settings = () => {
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={protocolId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Protocol
</label>
<select
id={protocolId}
value={formData.serverProtocol}
onChange={(e) =>
handleInputChange("serverProtocol", e.target.value)
@@ -486,10 +501,14 @@ const Settings = () => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={hostId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Host *
</label>
<input
id={hostId}
type="text"
value={formData.serverHost}
onChange={(e) =>
@@ -510,10 +529,14 @@ const Settings = () => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={portId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Port *
</label>
<input
id={portId}
type="number"
value={formData.serverPort}
onChange={(e) =>
@@ -551,13 +574,17 @@ const Settings = () => {
{/* Update Interval */}
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={updateIntervalId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Agent Update Interval (minutes)
</label>
{/* Numeric input (concise width) */}
<div className="flex items-center gap-2">
<input
id={updateIntervalId}
type="number"
min="5"
max="1440"
@@ -687,10 +714,14 @@ const Settings = () => {
{/* Default User Role Dropdown */}
{formData.signupEnabled && (
<div className="mt-3 ml-6">
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={defaultRoleId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
Default Role for New Users
</label>
<select
id={defaultRoleId}
value={formData.defaultUserRole}
onChange={(e) =>
handleInputChange("defaultUserRole", e.target.value)
@@ -989,10 +1020,10 @@ const Settings = () => {
</p>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<fieldset>
<legend className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
Repository Type
</label>
</legend>
<div className="space-y-2">
<div className="flex items-center">
<input
@@ -1038,13 +1069,17 @@ const Settings = () => {
Choose whether your repository is public or private to
determine the appropriate access method.
</p>
</div>
</fieldset>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={githubRepoUrlId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
GitHub Repository URL
</label>
<input
id={githubRepoUrlId}
type="text"
value={formData.githubRepoUrl || ""}
onChange={(e) =>
@@ -1084,10 +1119,14 @@ const Settings = () => {
{formData.useCustomSshKey && (
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
<label
htmlFor={sshKeyPathId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"
>
SSH Key Path
</label>
<input
id={sshKeyPathId}
type="text"
value={formData.sshKeyPath || ""}
onChange={(e) =>
@@ -1384,10 +1423,14 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
<form onSubmit={handleSubmit} className="px-6 py-4">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={versionId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Version *
</label>
<input
id={versionId}
type="text"
value={formData.version}
onChange={(e) =>
@@ -1408,10 +1451,14 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={releaseNotesId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Release Notes
</label>
<textarea
id={releaseNotesId}
value={formData.releaseNotes}
onChange={(e) =>
setFormData((prev) => ({
@@ -1426,7 +1473,10 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={scriptContentId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Script Content *
</label>
<div className="space-y-2">
@@ -1437,6 +1487,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
className="block w-full text-sm text-secondary-500 dark:text-secondary-400 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 dark:file:bg-primary-900 dark:file:text-primary-200"
/>
<textarea
id={scriptContentId}
value={formData.scriptContent}
onChange={(e) =>
setFormData((prev) => ({

View File

@@ -11,7 +11,7 @@ import {
User,
XCircle,
} from "lucide-react";
import React, { useState } from "react";
import React, { useId, useState } from "react";
import { useAuth } from "../contexts/AuthContext";
import { adminUsersAPI, permissionsAPI } from "../utils/api";
@@ -290,6 +290,13 @@ const Users = () => {
// Add User Modal Component
const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
const usernameId = useId();
const emailId = useId();
const firstNameId = useId();
const lastNameId = useId();
const passwordId = useId();
const roleId = useId();
const [formData, setFormData] = useState({
username: "",
email: "",
@@ -343,10 +350,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={usernameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Username
</label>
<input
id={usernameId}
type="text"
name="username"
required
@@ -357,10 +368,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={emailId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Email
</label>
<input
id={emailId}
type="email"
name="email"
required
@@ -372,10 +387,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={firstNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
First Name
</label>
<input
id={firstNameId}
type="text"
name="first_name"
value={formData.first_name}
@@ -384,10 +403,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
/>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={lastNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Last Name
</label>
<input
id={lastNameId}
type="text"
name="last_name"
value={formData.last_name}
@@ -398,10 +421,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={passwordId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Password
</label>
<input
id={passwordId}
type="password"
name="password"
required
@@ -416,10 +443,14 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={roleId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Role
</label>
<select
id={roleId}
name="role"
value={formData.role}
onChange={handleInputChange}
@@ -473,6 +504,13 @@ const AddUserModal = ({ isOpen, onClose, onUserCreated, roles }) => {
// Edit User Modal Component
const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
const editUsernameId = useId();
const editEmailId = useId();
const editFirstNameId = useId();
const editLastNameId = useId();
const editRoleId = useId();
const editActiveId = useId();
const [formData, setFormData] = useState({
username: user?.username || "",
email: user?.email || "",
@@ -518,10 +556,14 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editUsernameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Username
</label>
<input
id={editUsernameId}
type="text"
name="username"
required
@@ -532,10 +574,14 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editEmailId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Email
</label>
<input
id={editEmailId}
type="email"
name="email"
required
@@ -547,10 +593,14 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editFirstNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
First Name
</label>
<input
id={editFirstNameId}
type="text"
name="first_name"
value={formData.first_name}
@@ -559,10 +609,14 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
/>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editLastNameId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Last Name
</label>
<input
id={editLastNameId}
type="text"
name="last_name"
value={formData.last_name}
@@ -573,10 +627,14 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={editRoleId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Role
</label>
<select
id={editRoleId}
name="role"
value={formData.role}
onChange={handleInputChange}
@@ -600,13 +658,17 @@ const EditUserModal = ({ user, isOpen, onClose, onUserUpdated, roles }) => {
<div className="flex items-center">
<input
id={editActiveId}
type="checkbox"
name="is_active"
checked={formData.is_active}
onChange={handleInputChange}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 rounded"
/>
<label className="ml-2 block text-sm text-secondary-700 dark:text-secondary-200">
<label
htmlFor={editActiveId}
className="ml-2 block text-sm text-secondary-700 dark:text-secondary-200"
>
Active user
</label>
</div>
@@ -649,6 +711,8 @@ const ResetPasswordModal = ({
onPasswordReset,
isLoading,
}) => {
const newPasswordId = useId();
const confirmPasswordId = useId();
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
@@ -696,10 +760,14 @@ const ResetPasswordModal = ({
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={newPasswordId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
New Password
</label>
<input
id={newPasswordId}
type="password"
required
minLength={6}
@@ -711,10 +779,14 @@ const ResetPasswordModal = ({
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1">
<label
htmlFor={confirmPasswordId}
className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-1"
>
Confirm Password
</label>
<input
id={confirmPasswordId}
type="password"
required
value={confirmPassword}