diff --git a/frontend/index.html b/frontend/index.html index 8449b96..d2c7d72 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,19 @@ + MCP Hub Dashboard + + + +
+ \ No newline at end of file diff --git a/frontend/src/components/AddGroupForm.tsx b/frontend/src/components/AddGroupForm.tsx index ad40ef6..f0548c4 100644 --- a/frontend/src/components/AddGroupForm.tsx +++ b/frontend/src/components/AddGroupForm.tsx @@ -50,7 +50,7 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => { } const result = await createGroup(formData.name, formData.description, formData.servers) - + if (!result) { setError(t('groups.createError')) setIsSubmitting(false) @@ -69,7 +69,7 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => {

{t('groups.addNew')}

- + {error && (
{error} @@ -87,7 +87,7 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => { name="name" value={formData.name} onChange={handleChange} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder={t('groups.namePlaceholder')} required /> @@ -109,14 +109,14 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => {
@@ -290,7 +290,7 @@ const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) => @@ -301,7 +301,7 @@ const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) => handleInstallServer(serverName); }} disabled={isUploading} - className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 flex items-center" + className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 flex items-center btn-primary" > {isUploading ? ( <> @@ -344,7 +344,7 @@ const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) => className={`relative border-2 border-dashed rounded-lg p-8 text-center transition-colors ${isDragging ? 'border-blue-500 bg-blue-50' : selectedFile - ? 'border-green-500 bg-green-50' + ? 'border-gray-500 ' : 'border-gray-300 hover:border-gray-400' }`} onDragOver={handleDragOver} @@ -353,7 +353,7 @@ const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) => > {selectedFile ? (
- +

{selectedFile.name}

@@ -383,14 +383,14 @@ const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) =>
))} diff --git a/frontend/src/components/LogViewer.tsx b/frontend/src/components/LogViewer.tsx index 4d857bf..632de9d 100644 --- a/frontend/src/components/LogViewer.tsx +++ b/frontend/src/components/LogViewer.tsx @@ -48,25 +48,26 @@ const LogViewer: React.FC = ({ logs, isLoading = false, error = // Get badge color based on log type const getLogTypeColor = (type: string) => { switch (type) { - case 'error': return 'bg-red-400'; - case 'warn': return 'bg-yellow-400'; - case 'debug': return 'bg-purple-400'; - default: return 'bg-blue-400'; + case 'error': return 'bg-red-400/80 text-white'; + case 'warn': return 'bg-yellow-400/80 text-gray-900'; + case 'debug': return 'bg-purple-400/80 text-white'; + case 'info': return 'bg-blue-400/80 text-white'; + default: return 'bg-blue-400/80 text-white'; } }; // Get badge color based on log source const getSourceColor = (source: string) => { switch (source) { - case 'main': return 'bg-green-400'; - case 'child': return 'bg-orange-400'; - default: return 'bg-gray-400'; + case 'main': return 'bg-green-400/80 text-white'; + case 'child': return 'bg-orange-400/80 text-white'; + default: return 'bg-gray-400/80 text-white'; } }; return (
-
+
{t('logs.filters')}: @@ -74,14 +75,14 @@ const LogViewer: React.FC = ({ logs, isLoading = false, error = setFilter(e.target.value)} /> {/* Log type filters */}
- {(['info', 'error', 'warn', 'debug'] as const).map(type => ( + {(['debug', 'info', 'error', 'warn'] as const).map(type => ( = ({ logs, isLoading = false, error = variant="outline" size="sm" onClick={onClear} + className='btn-secondary' disabled={isLoading || logs.length === 0} > {t('logs.clearLogs')} @@ -164,7 +166,7 @@ const LogViewer: React.FC = ({ logs, isLoading = false, error = filteredLogs.map((log, index) => (
diff --git a/frontend/src/components/MarketServerCard.tsx b/frontend/src/components/MarketServerCard.tsx index ad04768..3bd4dd5 100644 --- a/frontend/src/components/MarketServerCard.tsx +++ b/frontend/src/components/MarketServerCard.tsx @@ -15,31 +15,31 @@ const MarketServerCard: React.FC = ({ server, onClick }) if (!server.tags || server.tags.length === 0) { return { tagsToShow: [], hasMore: false, moreCount: 0 }; } - + // Estimate available width in the card (in characters) const estimatedAvailableWidth = 28; // Estimated number of characters that can fit in one line - + // Calculate the character space needed for tags and plus sign (including # and spacing) const calculateTagWidth = (tag: string) => tag.length + 3; // +3 for # and spacing - + // Loop to determine the maximum number of tags that can be displayed let totalWidth = 0; let i = 0; - + // First, sort tags by length to prioritize displaying shorter tags const sortedTags = [...server.tags].sort((a, b) => a.length - b.length); - + // Calculate how many tags can fit for (i = 0; i < sortedTags.length; i++) { const tagWidth = calculateTagWidth(sortedTags[i]); - + // If this tag would make the total width exceed available width, stop adding if (totalWidth + tagWidth > estimatedAvailableWidth) { break; } - + totalWidth += tagWidth; - + // If this is the last tag but there's still space, no need to show "more" if (i === sortedTags.length - 1) { return { @@ -49,16 +49,16 @@ const MarketServerCard: React.FC = ({ server, onClick }) }; } } - + // If there's not enough space to display any tags, show at least one if (i === 0 && sortedTags.length > 0) { i = 1; } - + // Calculate space needed for the "more" tag const moreCount = sortedTags.length - i; const moreTagWidth = 3 + String(moreCount).length + t('market.moreTags').length; - + // If there's enough remaining space to display the "more" tag if (totalWidth + moreTagWidth <= estimatedAvailableWidth || i < 1) { return { @@ -67,7 +67,7 @@ const MarketServerCard: React.FC = ({ server, onClick }) moreCount }; } - + // If there's not enough space for even the "more" tag, reduce one tag to make room return { tagsToShow: sortedTags.slice(0, Math.max(1, i - 1)), @@ -79,27 +79,27 @@ const MarketServerCard: React.FC = ({ server, onClick }) const { tagsToShow, hasMore, moreCount } = getTagsToDisplay(); return ( -
onClick(server)} >

{server.display_name}

{server.is_official && ( - + {t('market.official')} )}

{server.description}

- + {/* Categories */}
{server.categories?.length > 0 ? ( server.categories.map((category, index) => ( - {category} @@ -108,15 +108,15 @@ const MarketServerCard: React.FC = ({ server, onClick }) - )}
- + {/* Tags */}
{server.tags?.length > 0 ? (
{tagsToShow.map((tag, index) => ( - #{tag} @@ -131,8 +131,8 @@ const MarketServerCard: React.FC = ({ server, onClick }) - )}
- -
+ +
{t('market.by')} diff --git a/frontend/src/components/MarketServerDetail.tsx b/frontend/src/components/MarketServerDetail.tsx index c170950..9b909c6 100644 --- a/frontend/src/components/MarketServerDetail.tsx +++ b/frontend/src/components/MarketServerDetail.tsx @@ -40,7 +40,7 @@ const MarketServerDetail: React.FC = ({ }; } else { return { - className: "bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm font-medium text-white", + className: "bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm font-medium text-white btn-primary", disabled: false, text: t('market.install') }; @@ -72,13 +72,13 @@ const MarketServerDetail: React.FC = ({ } else if (server.installations.default) { return server.installations.default; } - + // If none of the preferred types are available, get the first available installation type const installTypes = Object.keys(server.installations); if (installTypes.length > 0) { return server.installations[installTypes[0]]; } - + return undefined; }; @@ -114,15 +114,15 @@ const MarketServerDetail: React.FC = ({

- {server.display_name} + {server.display_name} ({server.name}) - {t('market.author')}: {server.author.name} • {t('market.license')}: {server.license} • + {t('market.author')}: {server.author.name} • {t('market.license')}: {server.license} • {t('market.repository')} @@ -132,7 +132,7 @@ const MarketServerDetail: React.FC = ({
{server.is_official && ( - + {t('market.official')} )} @@ -169,7 +169,7 @@ const MarketServerDetail: React.FC = ({

{t('market.arguments')}

- +
{t('market.argumentName')} @@ -198,7 +198,7 @@ const MarketServerDetail: React.FC = ({ {arg.required ? ( ) : ( - + )} @@ -228,7 +228,7 @@ const MarketServerDetail: React.FC = ({ element.classList.toggle('hidden'); } }} - className="text-sm text-blue-600 hover:underline focus:outline-none ml-2" + className="text-sm text-blue-500 font-normal hover:underline focus:outline-none ml-2" > {t('market.viewSchema')} @@ -281,12 +281,12 @@ const MarketServerDetail: React.FC = ({ initialData={{ name: server.name, status: 'disconnected', - config: preferredInstallation + config: preferredInstallation ? { - command: preferredInstallation.command || '', - args: preferredInstallation.args || [], - env: preferredInstallation.env || {} - } + command: preferredInstallation.command || '', + args: preferredInstallation.args || [], + env: preferredInstallation.env || {} + } : undefined }} /> diff --git a/frontend/src/components/ServerCard.tsx b/frontend/src/components/ServerCard.tsx index 4d4f0ae..64fd2ff 100644 --- a/frontend/src/components/ServerCard.tsx +++ b/frontend/src/components/ServerCard.tsx @@ -128,7 +128,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar return ( <> -
+
setIsExpanded(!isExpanded)} @@ -138,7 +138,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar {/* Tool count display */} -
+
@@ -174,7 +174,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar

{t('server.errorDetails')}

@@ -211,8 +211,8 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar className={`px-3 py-1 text-sm rounded transition-colors ${isToggling ? 'bg-gray-200 text-gray-500' : server.enabled !== false - ? 'bg-green-100 text-green-800 hover:bg-green-200' - : 'bg-gray-100 text-gray-800 hover:bg-gray-200' + ? 'bg-green-100 text-green-800 hover:bg-green-200 btn-secondary' + : 'bg-gray-100 text-gray-800 hover:bg-gray-200 btn-primary' }`} disabled={isToggling} > @@ -226,11 +226,11 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
-
diff --git a/frontend/src/components/ServerForm.tsx b/frontend/src/components/ServerForm.tsx index e0e4452..75457e6 100644 --- a/frontend/src/components/ServerForm.tsx +++ b/frontend/src/components/ServerForm.tsx @@ -264,7 +264,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr

{modalTitle}

-
@@ -286,7 +286,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr id="name" value={formData.name} onChange={handleInputChange} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder="e.g.: time-mcp" required disabled={isEdit} @@ -403,7 +403,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi!, url: e.target.value } }))} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder="e.g.: https://api.example.com/openapi.json" required={serverType === 'openapi' && formData.openapi?.inputMode === 'url'} /> @@ -462,7 +462,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr url: prev.openapi?.url || '' } }))} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" > @@ -474,7 +474,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr {/* API Key Configuration */} {formData.openapi?.securityType === 'apiKey' && ( -
+

{t('server.openapi.apiKeyConfig')}

@@ -486,7 +486,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, apiKeyName: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm form-input focus:outline-none" placeholder="Authorization" />
@@ -498,7 +498,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, apiKeyIn: e.target.value as any, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" > @@ -514,7 +514,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, apiKeyValue: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" placeholder="your-api-key" />
@@ -524,7 +524,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr {/* HTTP Authentication Configuration */} {formData.openapi?.securityType === 'http' && ( -
+

{t('server.openapi.httpAuthConfig')}

@@ -535,7 +535,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, httpScheme: e.target.value as any, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" > @@ -551,7 +551,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, httpCredentials: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" placeholder={formData.openapi?.httpScheme === 'basic' ? 'base64-encoded-credentials' : 'bearer-token'} />
@@ -561,7 +561,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr {/* OAuth2 Configuration */} {formData.openapi?.securityType === 'oauth2' && ( -
+

{t('server.openapi.oauth2Config')}

@@ -573,7 +573,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, oauth2Token: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" placeholder="access-token" />
@@ -583,7 +583,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr {/* OpenID Connect Configuration */} {formData.openapi?.securityType === 'openIdConnect' && ( -
+

{t('server.openapi.openIdConnectConfig')}

@@ -595,7 +595,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, openIdConnectUrl: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" placeholder="https://example.com/.well-known/openid_configuration" />
@@ -608,7 +608,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr ...prev, openapi: { ...prev.openapi, openIdConnectToken: e.target.value, url: prev.openapi?.url || '' } }))} - className="w-full border rounded px-2 py-1 text-sm" + className="w-full border rounded px-2 py-1 text-sm focus:outline-none form-input" placeholder="id-token" />
@@ -624,7 +624,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr @@ -636,7 +636,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={headerVar.key} onChange={(e) => handleHeaderVarChange(index, 'key', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder="Authorization" /> : @@ -644,14 +644,14 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={headerVar.value} onChange={(e) => handleHeaderVarChange(index, 'value', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder="Bearer token..." />
@@ -671,7 +671,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr id="url" value={formData.url} onChange={handleInputChange} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder={serverType === 'streamable-http' ? "e.g.: http://localhost:3000/mcp" : "e.g.: http://localhost:3000/sse"} required={serverType === 'sse' || serverType === 'streamable-http'} /> @@ -685,7 +685,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr @@ -697,7 +697,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={headerVar.key} onChange={(e) => handleHeaderVarChange(index, 'key', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder="Authorization" /> : @@ -705,14 +705,14 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={headerVar.value} onChange={(e) => handleHeaderVarChange(index, 'value', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder="Bearer token..." />
@@ -732,7 +732,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr id="command" value={formData.command} onChange={handleInputChange} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder="e.g.: npx" required={serverType === 'stdio'} /> @@ -747,7 +747,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr id="arguments" value={formData.arguments} onChange={(e) => handleArgsChange(e.target.value)} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder="e.g.: -y time-mcp" required={serverType === 'stdio'} /> @@ -761,7 +761,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr @@ -773,7 +773,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={envVar.key} onChange={(e) => handleEnvVarChange(index, 'key', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder={t('server.key')} /> : @@ -781,14 +781,14 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr type="text" value={envVar.value} onChange={(e) => handleEnvVarChange(index, 'value', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2 form-input" placeholder={t('server.value')} />
@@ -802,7 +802,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr {serverType !== 'openapi' && (
setIsRequestOptionsExpanded(!isRequestOptionsExpanded)} >
{isRequestOptionsExpanded && ( -
+