Compare commits

...

4 Commits

Author SHA1 Message Date
github-actions[bot]
e2b74c8db8 chore: add VERSION v0.2.5 2025-10-09 07:54:15 +00:00
Michel Roegl-Brunner
4ed3e42148 feat: Add sorting functionality to installed scripts table (#88)
* feat: Add sorting functionality to installed scripts table

- Add sortable columns for Script Name, Container ID, Server, Status, and Installation Date
- Implement clickable table headers with visual sort indicators
- Add ascending/descending toggle functionality
- Maintain existing search and filter functionality
- Default sort by Script Name (ascending)
- Handle null values gracefully in sorting logic

* fix: Replace logical OR with nullish coalescing operator

- Fix TypeScript ESLint errors in InstalledScriptsTab.tsx
- Replace || with ?? for safer null/undefined handling
- Build now passes successfully
2025-10-09 09:51:33 +02:00
Michel Roegl-Brunner
a09f331d5f add restart command for service 2025-10-09 09:34:36 +02:00
github-actions[bot]
36beb427c0 chore: add VERSION v0.2.4 (#87)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-09 07:20:38 +00:00
3 changed files with 125 additions and 25 deletions

View File

@@ -1 +1 @@
0.2.3
0.2.5

View File

@@ -26,6 +26,8 @@ export function InstalledScriptsTab() {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'failed' | 'in_progress'>('all');
const [serverFilter, setServerFilter] = useState<string>('all');
const [sortField, setSortField] = useState<'script_name' | 'container_id' | 'server_name' | 'status' | 'installation_date'>('script_name');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const [updatingScript, setUpdatingScript] = useState<{ id: number; containerId: string; server?: any } | null>(null);
const [editingScriptId, setEditingScriptId] = useState<number | null>(null);
const [editFormData, setEditFormData] = useState<{ script_name: string; container_id: string }>({ script_name: '', container_id: '' });
@@ -154,20 +156,58 @@ export function InstalledScriptsTab() {
}
}, [scripts.length, serversData?.servers, cleanupMutation]);
// Filter scripts based on search and filters
const filteredScripts = scripts.filter((script: InstalledScript) => {
const matchesSearch = script.script_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(script.container_id?.includes(searchTerm) ?? false) ||
(script.server_name?.toLowerCase().includes(searchTerm.toLowerCase()) ?? false);
const matchesStatus = statusFilter === 'all' || script.status === statusFilter;
const matchesServer = serverFilter === 'all' ||
(serverFilter === 'local' && !script.server_name) ||
(script.server_name === serverFilter);
return matchesSearch && matchesStatus && matchesServer;
});
// Filter and sort scripts
const filteredScripts = scripts
.filter((script: InstalledScript) => {
const matchesSearch = script.script_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(script.container_id?.includes(searchTerm) ?? false) ||
(script.server_name?.toLowerCase().includes(searchTerm.toLowerCase()) ?? false);
const matchesStatus = statusFilter === 'all' || script.status === statusFilter;
const matchesServer = serverFilter === 'all' ||
(serverFilter === 'local' && !script.server_name) ||
(script.server_name === serverFilter);
return matchesSearch && matchesStatus && matchesServer;
})
.sort((a: InstalledScript, b: InstalledScript) => {
let aValue: any;
let bValue: any;
switch (sortField) {
case 'script_name':
aValue = a.script_name.toLowerCase();
bValue = b.script_name.toLowerCase();
break;
case 'container_id':
aValue = a.container_id ?? '';
bValue = b.container_id ?? '';
break;
case 'server_name':
aValue = a.server_name ?? 'Local';
bValue = b.server_name ?? 'Local';
break;
case 'status':
aValue = a.status;
bValue = b.status;
break;
case 'installation_date':
aValue = new Date(a.installation_date).getTime();
bValue = new Date(b.installation_date).getTime();
break;
default:
return 0;
}
if (aValue < bValue) {
return sortDirection === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortDirection === 'asc' ? 1 : -1;
}
return 0;
});
// Get unique servers for filter
const uniqueServers: string[] = [];
@@ -298,6 +338,15 @@ export function InstalledScriptsTab() {
setAutoDetectServerId('');
};
const handleSort = (field: 'script_name' | 'container_id' | 'server_name' | 'status' | 'installation_date') => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString();
@@ -652,20 +701,70 @@ export function InstalledScriptsTab() {
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-muted">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Script Name
<th
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
onClick={() => handleSort('script_name')}
>
<div className="flex items-center space-x-1">
<span>Script Name</span>
{sortField === 'script_name' && (
<span className="text-primary">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Container ID
<th
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
onClick={() => handleSort('container_id')}
>
<div className="flex items-center space-x-1">
<span>Container ID</span>
{sortField === 'container_id' && (
<span className="text-primary">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Server
<th
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
onClick={() => handleSort('server_name')}
>
<div className="flex items-center space-x-1">
<span>Server</span>
{sortField === 'server_name' && (
<span className="text-primary">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Status
<th
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
onClick={() => handleSort('status')}
>
<div className="flex items-center space-x-1">
<span>Status</span>
{sortField === 'status' && (
<span className="text-primary">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Installation Date
<th
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
onClick={() => handleSort('installation_date')}
>
<div className="flex items-center space-x-1">
<span>Installation Date</span>
{sortField === 'installation_date' && (
<span className="text-primary">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Actions

View File

@@ -592,6 +592,7 @@ start_application() {
if [ "$SERVICE_WAS_RUNNING" = true ] && check_service; then
log "Service was running before update, re-enabling and starting systemd service..."
if systemctl enable --now pvescriptslocal.service; then
systemctl restart pvescriptslocal.service
log_success "Service enabled and started successfully"
# Wait a moment and check if it's running
sleep 2