Compare commits

...

7 Commits

Author SHA1 Message Date
Michel Roegl-Brunner
4a37e51d4d Clean up code: remove unused comments and variables
- Remove unnecessary comments in InstalledScriptsTab
- Clean up unused error variable in installedScripts router
- Minor code cleanup for better readability
2025-10-14 09:02:02 +02:00
Michel Roegl-Brunner
6d737e54bc Add tab persistence with localStorage
- Implement localStorage persistence for active tab selection
- Tab selection now survives page reloads and browser sessions
- Added lazy initialization to read saved tab from localStorage
- Added useEffect to automatically save tab changes to localStorage
- Includes SSR safety checks for Next.js compatibility
- Defaults to 'scripts' tab if no saved tab found
2025-10-14 08:59:06 +02:00
Michel Roegl-Brunner
8835abad15 Reduce footer size by approximately 50%
- Changed vertical padding from py-6 to py-3 (3rem to 1.5rem)
- Reduced gaps between elements from gap-4 to gap-2 throughout
- Made footer more compact while maintaining functionality
- Improved screen space utilization for main content
2025-10-14 08:58:01 +02:00
Michel Roegl-Brunner
fbf9e399db Improve button styling on available scripts page
- Made download button smaller and less colorful with subtle blue theme
- Updated Clear Selection and Select All Visible buttons to match Settings button styling
- Changed buttons to outline variant with default size for consistency
- Removed custom border styling to use standard outline appearance
- Improved visual hierarchy and button cohesion across the interface
2025-10-14 08:57:08 +02:00
Michel Roegl-Brunner
d8d5776bb7 Fix input field alignment in editing mode
- Improved vertical alignment of script name and container ID input fields
- Removed script path line during editing for cleaner interface
- Added consistent min-height and flex centering for both input fields
- Enhanced input styling with better padding and focus states
- Input fields now align perfectly at the same height level
2025-10-14 08:54:01 +02:00
Michel Roegl-Brunner
7f2fce95c1 Enhance stop/destroy button hover effects and colors
- Added scaling and shadow hover effects to stop/start and destroy buttons
- Enhanced color scheme with high-contrast colors:
  - Stop button: vibrant red (bg-red-600) with red glow shadow
  - Start button: vibrant green (bg-green-600) with green glow shadow
  - Destroy button: dark red (bg-red-800) with intense red glow shadow
- Added colored borders and smooth transitions for better visual feedback
- Improved button visibility and user experience with color-coded actions
2025-10-14 08:49:21 +02:00
Michel Roegl-Brunner
2aaae1e67b Improve navigation layout: reduce spacing and move help icons inside nav buttons
- Reduced horizontal spacing between nav items from sm:space-x-2 lg:space-x-8 to sm:space-x-1
- Moved ContextualHelpIcon components inside Button components for better UX
- Removed unnecessary wrapper divs to create more compact layout
- Help symbols are now part of the clickable nav area
2025-10-14 08:47:27 +02:00
5 changed files with 95 additions and 85 deletions

View File

@@ -12,10 +12,10 @@ export function Footer({ onOpenReleaseNotes }: FooterProps) {
const { data: versionData } = api.version.getCurrentVersion.useQuery();
return (
<footer className="sticky bottom-0 mt-auto border-t border-border bg-muted/30 py-6 backdrop-blur-sm">
<footer className="sticky bottom-0 mt-auto border-t border-border bg-muted/30 py-3 backdrop-blur-sm">
<div className="container mx-auto px-4">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-4">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
<div className="flex items-center gap-2">
<span>© 2024 PVE Scripts Local</span>
{versionData?.success && versionData.version && (
<Button
@@ -29,7 +29,7 @@ export function Footer({ onOpenReleaseNotes }: FooterProps) {
)}
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"

View File

@@ -322,7 +322,7 @@ export function InstalledScriptsTab() {
if (serverIds.length > 0) {
containerStatusMutation.mutate({ serverIds });
}
}, []); // Empty dependency array to prevent infinite loops
}, []);
// Run cleanup when component mounts and scripts are loaded (only once)
useEffect(() => {
@@ -333,17 +333,12 @@ export function InstalledScriptsTab() {
}, [scripts.length, serversData?.servers, cleanupMutation]);
// Note: Individual status fetching removed - using bulk fetchContainerStatuses instead
// Trigger status check when tab becomes active (component mounts)
useEffect(() => {
if (scripts.length > 0) {
fetchContainerStatuses();
}
}, [scripts.length]); // Only depend on scripts.length to prevent infinite loops
// Update scripts with container statuses
const scriptsWithStatus = scripts.map(script => ({
...script,
container_status: script.container_id ? containerStatuses.get(script.id) ?? 'unknown' : undefined
@@ -1067,15 +1062,14 @@ export function InstalledScriptsTab() {
>
<td className="px-6 py-4 whitespace-nowrap">
{editingScriptId === script.id ? (
<div className="space-y-2">
<div className="flex items-center min-h-[2.5rem]">
<input
type="text"
value={editFormData.script_name}
onChange={(e) => handleInputChange('script_name', e.target.value)}
className="w-full px-2 py-1 text-sm border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
className="w-full px-3 py-2 text-sm font-medium border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
placeholder="Script name"
/>
<div className="text-xs text-muted-foreground">{script.script_path}</div>
</div>
) : (
<div>
@@ -1086,13 +1080,15 @@ export function InstalledScriptsTab() {
</td>
<td className="px-6 py-4 whitespace-nowrap">
{editingScriptId === script.id ? (
<input
type="text"
value={editFormData.container_id}
onChange={(e) => handleInputChange('container_id', e.target.value)}
className="w-full px-2 py-1 text-sm font-mono border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
placeholder="Container ID"
/>
<div className="flex items-center min-h-[2.5rem]">
<input
type="text"
value={editFormData.container_id}
onChange={(e) => handleInputChange('container_id', e.target.value)}
className="w-full px-3 py-2 text-sm font-mono border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
placeholder="Container ID"
/>
</div>
) : (
script.container_id ? (
<div className="flex items-center space-x-2">
@@ -1187,6 +1183,10 @@ export function InstalledScriptsTab() {
disabled={controllingScriptId === script.id || (containerStatuses.get(script.id) ?? 'unknown') === 'unknown'}
variant={(containerStatuses.get(script.id) ?? 'unknown') === 'running' ? 'destructive' : 'default'}
size="sm"
className={(containerStatuses.get(script.id) ?? 'unknown') === 'running'
? "bg-red-600 hover:bg-red-700 text-white border border-red-500 hover:border-red-400 hover:scale-105 hover:shadow-lg hover:shadow-red-500/25 transition-all duration-200 disabled:hover:scale-100"
: "bg-green-600 hover:bg-green-700 text-white border border-green-500 hover:border-green-400 hover:scale-105 hover:shadow-lg hover:shadow-green-500/25 transition-all duration-200 disabled:hover:scale-100"
}
>
{controllingScriptId === script.id ? 'Working...' : (containerStatuses.get(script.id) ?? 'unknown') === 'running' ? 'Stop' : 'Start'}
</Button>
@@ -1195,6 +1195,7 @@ export function InstalledScriptsTab() {
disabled={controllingScriptId === script.id}
variant="destructive"
size="sm"
className="bg-red-800 hover:bg-red-900 text-white border border-red-600 hover:border-red-500 hover:scale-105 hover:shadow-lg hover:shadow-red-600/30 transition-all duration-200 disabled:hover:scale-100"
>
{controllingScriptId === script.id ? 'Working...' : 'Destroy'}
</Button>

View File

@@ -626,11 +626,13 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
<Button
onClick={handleBatchDownload}
disabled={loadSingleScriptMutation.isPending}
className="bg-blue-600 hover:bg-blue-700 text-white"
variant="outline"
size="sm"
className="bg-blue-500/10 hover:bg-blue-500/20 border-blue-500/30 text-blue-300 hover:text-blue-200 hover:border-blue-400/50"
>
{loadSingleScriptMutation.isPending ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2"></div>
Downloading...
</>
) : (
@@ -642,6 +644,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
onClick={handleDownloadAllFiltered}
disabled={filteredScripts.length === 0 || loadSingleScriptMutation.isPending}
variant="outline"
size="sm"
>
{loadSingleScriptMutation.isPending ? (
<>
@@ -657,8 +660,8 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
{selectedSlugs.size > 0 && (
<Button
onClick={clearSelection}
variant="ghost"
size="sm"
variant="outline"
size="default"
>
Clear Selection
</Button>
@@ -667,8 +670,8 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
{filteredScripts.length > 0 && (
<Button
onClick={selectAllVisible}
variant="ghost"
size="sm"
variant="outline"
size="default"
>
Select All Visible
</Button>

View File

@@ -20,7 +20,13 @@ import { api } from '~/trpc/react';
export default function Home() {
const [runningScript, setRunningScript] = useState<{ path: string; name: string; mode?: 'local' | 'ssh'; server?: any } | null>(null);
const [activeTab, setActiveTab] = useState<'scripts' | 'downloaded' | 'installed'>('scripts');
const [activeTab, setActiveTab] = useState<'scripts' | 'downloaded' | 'installed'>(() => {
if (typeof window !== 'undefined') {
const savedTab = localStorage.getItem('activeTab') as 'scripts' | 'downloaded' | 'installed';
return savedTab || 'scripts';
}
return 'scripts';
});
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false);
const [highlightVersion, setHighlightVersion] = useState<string | undefined>(undefined);
const terminalRef = useRef<HTMLDivElement>(null);
@@ -31,6 +37,13 @@ export default function Home() {
const { data: installedScriptsData } = api.installedScripts.getAllInstalledScripts.useQuery();
const { data: versionData } = api.version.getCurrentVersion.useQuery();
// Save active tab to localStorage whenever it changes
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('activeTab', activeTab);
}
}, [activeTab]);
// Auto-show release notes modal after update
useEffect(() => {
if (versionData?.success && versionData.version) {
@@ -131,64 +144,58 @@ export default function Home() {
{/* Tab Navigation */}
<div className="mb-6 sm:mb-8">
<div className="border-b border-border">
<nav className="-mb-px flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 lg:space-x-8">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('scripts')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'scripts'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<Package className="h-4 w-4" />
<span className="hidden sm:inline">Available Scripts</span>
<span className="sm:hidden">Available</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.available}
</span>
</Button>
<nav className="-mb-px flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-1">
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('scripts')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'scripts'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<Package className="h-4 w-4" />
<span className="hidden sm:inline">Available Scripts</span>
<span className="sm:hidden">Available</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.available}
</span>
<ContextualHelpIcon section="available-scripts" tooltip="Help with Available Scripts" />
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('downloaded')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'downloaded'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<HardDrive className="h-4 w-4" />
<span className="hidden sm:inline">Downloaded Scripts</span>
<span className="sm:hidden">Downloaded</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.downloaded}
</span>
</Button>
</Button>
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('downloaded')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'downloaded'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<HardDrive className="h-4 w-4" />
<span className="hidden sm:inline">Downloaded Scripts</span>
<span className="sm:hidden">Downloaded</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.downloaded}
</span>
<ContextualHelpIcon section="downloaded-scripts" tooltip="Help with Downloaded Scripts" />
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('installed')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'installed'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<FolderOpen className="h-4 w-4" />
<span className="hidden sm:inline">Installed Scripts</span>
<span className="sm:hidden">Installed</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.installed}
</span>
</Button>
</Button>
<Button
variant="ghost"
size="null"
onClick={() => setActiveTab('installed')}
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
activeTab === 'installed'
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
}`}>
<FolderOpen className="h-4 w-4" />
<span className="hidden sm:inline">Installed Scripts</span>
<span className="sm:hidden">Installed</span>
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
{scriptCounts.installed}
</span>
<ContextualHelpIcon section="installed-scripts" tooltip="Help with Installed Scripts" />
</div>
</Button>
</nav>
</div>
</div>

View File

@@ -906,9 +906,8 @@ export const installedScriptsRouter = createTRPCRouter({
);
});
}
} catch (_error) {
// If status check fails, continue with destroy attempt
// The destroy command will handle the error appropriately
} catch {
}
// Execute destroy command