Compare commits

...

6 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
Michel Roegl-Brunner
ca2cbd5a7f Fix terminal colors and functionality issues (#86)
* Fix terminal colors and stop button functionality

- Updated terminal theme to GitHub Dark with proper ANSI color support
- Fixed terminal background and foreground colors for better readability
- Removed aggressive CSS overrides that were breaking ANSI color handling
- Fixed stop button restarting script execution issue
- Added isStopped state to prevent automatic script restart after stop
- Improved WebSocket connection stability to prevent duplicate executions
- Fixed cursor rendering issues in whiptail sessions
- Enhanced terminal styling with proper color palette configuration

* Fix downloaded scripts terminal functionality

- Add install functionality to DownloadedScriptsTab component
- Pass onInstallScript prop from main page to DownloadedScriptsTab
- Enable terminal display when installing from downloaded scripts tab
- Maintain consistency with available scripts tab functionality
- Fix missing terminal integration for downloaded script installations

* Improve mobile terminal focus behavior

- Add terminal ref to main page for precise scrolling
- Update scroll behavior to focus terminal instead of page top
- Add mobile-specific offset for better terminal visibility
- Remove generic page scroll from ScriptDetailModal
- Ensure terminal is properly focused when starting installations on mobile
2025-10-09 09:19:55 +02:00
github-actions[bot]
d6803b99a6 chore: add VERSION v0.2.3 (#82)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-08 14:26:12 +00:00
10 changed files with 292 additions and 69 deletions

View File

@@ -1 +1 @@
0.2.2 0.2.5

43
scripts/ct/debian.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(dirname "$0")"
source "$SCRIPT_DIR/../core/build.func"
# Copyright (c) 2021-2025 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://www.debian.org/
APP="Debian"
var_tags="${var_tags:-os}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /var ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating $APP LXC"
$STD apt update
$STD apt -y upgrade
msg_ok "Updated $APP LXC"
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://www.debian.org/
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
motd_ssh
customize
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -9,7 +9,16 @@ import { FilterBar, type FilterState } from './FilterBar';
import { Button } from './ui/button'; import { Button } from './ui/button';
import type { ScriptCard as ScriptCardType } from '~/types/script'; import type { ScriptCard as ScriptCardType } from '~/types/script';
export function DownloadedScriptsTab() { interface DownloadedScriptsTabProps {
onInstallScript?: (
scriptPath: string,
scriptName: string,
mode?: "local" | "ssh",
server?: any,
) => void;
}
export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabProps) {
const [selectedSlug, setSelectedSlug] = useState<string | null>(null); const [selectedSlug, setSelectedSlug] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null); const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
@@ -462,9 +471,7 @@ export function DownloadedScriptsTab() {
script={scriptData?.success ? scriptData.script : null} script={scriptData?.success ? scriptData.script : null}
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={handleCloseModal} onClose={handleCloseModal}
onInstallScript={() => { onInstallScript={onInstallScript}
// Downloaded scripts don't need installation
}}
/> />
</div> </div>
</div> </div>

View File

@@ -26,6 +26,8 @@ export function InstalledScriptsTab() {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'failed' | 'in_progress'>('all'); const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'failed' | 'in_progress'>('all');
const [serverFilter, setServerFilter] = useState<string>('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 [updatingScript, setUpdatingScript] = useState<{ id: number; containerId: string; server?: any } | null>(null);
const [editingScriptId, setEditingScriptId] = useState<number | null>(null); const [editingScriptId, setEditingScriptId] = useState<number | null>(null);
const [editFormData, setEditFormData] = useState<{ script_name: string; container_id: string }>({ script_name: '', container_id: '' }); 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]); }, [scripts.length, serversData?.servers, cleanupMutation]);
// Filter scripts based on search and filters // Filter and sort scripts
const filteredScripts = scripts.filter((script: InstalledScript) => { const filteredScripts = scripts
const matchesSearch = script.script_name.toLowerCase().includes(searchTerm.toLowerCase()) || .filter((script: InstalledScript) => {
(script.container_id?.includes(searchTerm) ?? false) || const matchesSearch = script.script_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(script.server_name?.toLowerCase().includes(searchTerm.toLowerCase()) ?? false); (script.container_id?.includes(searchTerm) ?? false) ||
(script.server_name?.toLowerCase().includes(searchTerm.toLowerCase()) ?? false);
const matchesStatus = statusFilter === 'all' || script.status === statusFilter;
const matchesStatus = statusFilter === 'all' || script.status === statusFilter;
const matchesServer = serverFilter === 'all' ||
(serverFilter === 'local' && !script.server_name) || const matchesServer = serverFilter === 'all' ||
(script.server_name === serverFilter); (serverFilter === 'local' && !script.server_name) ||
(script.server_name === serverFilter);
return matchesSearch && matchesStatus && matchesServer;
}); 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 // Get unique servers for filter
const uniqueServers: string[] = []; const uniqueServers: string[] = [];
@@ -298,6 +338,15 @@ export function InstalledScriptsTab() {
setAutoDetectServerId(''); 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) => { const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString(); return new Date(dateString).toLocaleString();
@@ -652,20 +701,70 @@ export function InstalledScriptsTab() {
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-gray-200">
<thead className="bg-muted"> <thead className="bg-muted">
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th
Script Name 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>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th
Container ID 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>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th
Server 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>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th
Status 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>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th
Installation Date 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>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Actions Actions

View File

@@ -120,9 +120,6 @@ export function ScriptDetailModal({
// Pass execution mode and server info to the parent // Pass execution mode and server info to the parent
onInstallScript(scriptPath, scriptName, mode, server); onInstallScript(scriptPath, scriptName, mode, server);
// Scroll to top of the page to see the terminal
window.scrollTo({ top: 0, behavior: "smooth" });
onClose(); // Close the modal when starting installation onClose(); // Close the modal when starting installation
} }
}; };

View File

@@ -29,6 +29,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
const [lastInputSent, setLastInputSent] = useState<string | null>(null); const [lastInputSent, setLastInputSent] = useState<string | null>(null);
const [inWhiptailSession, setInWhiptailSession] = useState(false); const [inWhiptailSession, setInWhiptailSession] = useState(false);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const [isStopped, setIsStopped] = useState(false);
const terminalRef = useRef<HTMLDivElement>(null); const terminalRef = useRef<HTMLDivElement>(null);
const xtermRef = useRef<any>(null); const xtermRef = useRef<any>(null);
const fitAddonRef = useRef<any>(null); const fitAddonRef = useRef<any>(null);
@@ -64,23 +65,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) { if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) {
// This is a clear screen sequence, ensure it's processed correctly // This is a clear screen sequence, ensure it's processed correctly
xtermRef.current.write(message.data); xtermRef.current.write(message.data);
} else if (message.data.includes('\x1b[') && message.data.includes('H')) {
// This is a cursor positioning sequence, often implies a redraw of the entire screen
if (inWhiptailSession) {
// In whiptail session, completely reset the terminal
// Completely clear everything
xtermRef.current.clear();
xtermRef.current.write('\x1b[2J\x1b[H\x1b[3J\x1b[2J');
// Reset the terminal state
xtermRef.current.reset();
// Write the new content after reset
setTimeout(() => {
xtermRef.current.write(message.data);
}, 100);
} else {
xtermRef.current.write(message.data);
}
} else { } else {
// Let xterm handle all ANSI sequences naturally
xtermRef.current.write(message.data); xtermRef.current.write(message.data);
} }
break; break;
@@ -153,9 +139,27 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
const terminal = new XTerm({ const terminal = new XTerm({
theme: { theme: {
background: '#000000', background: '#0d1117',
foreground: '#00ff00', foreground: '#e6edf3',
cursor: '#00ff00', cursor: '#58a6ff',
cursorAccent: '#0d1117',
// Let ANSI colors work naturally - only define basic colors
black: '#484f58',
red: '#f85149',
green: '#3fb950',
yellow: '#d29922',
blue: '#58a6ff',
magenta: '#bc8cff',
cyan: '#39d353',
white: '#b1bac4',
brightBlack: '#6e7681',
brightRed: '#ff7b72',
brightGreen: '#56d364',
brightYellow: '#e3b341',
brightBlue: '#79c0ff',
brightMagenta: '#d2a8ff',
brightCyan: '#56d364',
brightWhite: '#f0f6fc',
}, },
fontSize: isMobile ? 7 : 14, fontSize: isMobile ? 7 : 14,
fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace', fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace',
@@ -189,6 +193,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
// Open terminal // Open terminal
terminal.open(terminalElement); terminal.open(terminalElement);
// Ensure proper terminal rendering
setTimeout(() => {
terminal.refresh(0, terminal.rows - 1);
// Ensure cursor is properly positioned
terminal.focus();
}, 100);
// Fit after a small delay to ensure proper sizing // Fit after a small delay to ensure proper sizing
setTimeout(() => { setTimeout(() => {
fitAddon.fit(); fitAddon.fit();
@@ -269,6 +280,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
} }
isConnectingRef.current = true; isConnectingRef.current = true;
const isInitialConnection = !hasConnectedRef.current;
hasConnectedRef.current = true; hasConnectedRef.current = true;
// Small delay to prevent rapid reconnection // Small delay to prevent rapid reconnection
@@ -284,17 +296,19 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
setIsConnected(true); setIsConnected(true);
isConnectingRef.current = false; isConnectingRef.current = false;
// Send start message immediately after connection // Only auto-start on initial connection, not on reconnections
const message = { if (isInitialConnection && !isRunning) {
action: 'start', const message = {
scriptPath, action: 'start',
executionId, scriptPath,
mode, executionId,
server, mode,
isUpdate, server,
containerId isUpdate,
}; containerId
ws.send(JSON.stringify(message)); };
ws.send(JSON.stringify(message));
}
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
@@ -335,7 +349,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
}, [scriptPath, executionId, mode, server, isUpdate, containerId, handleMessage, isMobile]); }, [scriptPath, executionId, mode, server, isUpdate, containerId, handleMessage, isMobile]);
const startScript = () => { const startScript = () => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN && !isRunning) {
setIsStopped(false);
wsRef.current.send(JSON.stringify({ wsRef.current.send(JSON.stringify({
action: 'start', action: 'start',
scriptPath, scriptPath,
@@ -350,6 +365,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
const stopScript = () => { const stopScript = () => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
setIsStopped(true);
setIsRunning(false);
wsRef.current.send(JSON.stringify({ wsRef.current.send(JSON.stringify({
action: 'stop', action: 'stop',
executionId executionId
@@ -586,10 +603,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
<div className="flex flex-wrap gap-1 sm:gap-2"> <div className="flex flex-wrap gap-1 sm:gap-2">
<Button <Button
onClick={startScript} onClick={startScript}
disabled={!isConnected || isRunning} disabled={!isConnected || (isRunning && !isStopped)}
variant="default" variant="default"
size="sm" size="sm"
className={`text-xs sm:text-sm ${isConnected && !isRunning ? 'bg-green-600 hover:bg-green-700' : 'bg-muted text-muted-foreground cursor-not-allowed'}`} className={`text-xs sm:text-sm ${isConnected && (!isRunning || isStopped) ? 'bg-green-600 hover:bg-green-700' : 'bg-muted text-muted-foreground cursor-not-allowed'}`}
> >
<Play className="h-3 w-3 sm:h-4 sm:w-4 mr-1" /> <Play className="h-3 w-3 sm:h-4 sm:w-4 mr-1" />
<span className="hidden sm:inline">Start</span> <span className="hidden sm:inline">Start</span>

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useRef } from 'react';
import { ScriptsGrid } from './_components/ScriptsGrid'; import { ScriptsGrid } from './_components/ScriptsGrid';
import { DownloadedScriptsTab } from './_components/DownloadedScriptsTab'; import { DownloadedScriptsTab } from './_components/DownloadedScriptsTab';
import { InstalledScriptsTab } from './_components/InstalledScriptsTab'; import { InstalledScriptsTab } from './_components/InstalledScriptsTab';
@@ -16,9 +16,25 @@ import { Rocket, Package, HardDrive, FolderOpen } from 'lucide-react';
export default function Home() { export default function Home() {
const [runningScript, setRunningScript] = useState<{ path: string; name: string; mode?: 'local' | 'ssh'; server?: any } | null>(null); 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'>('scripts');
const terminalRef = useRef<HTMLDivElement>(null);
const scrollToTerminal = () => {
if (terminalRef.current) {
// Get the element's position and scroll with a small offset for better mobile experience
const elementTop = terminalRef.current.offsetTop;
const offset = window.innerWidth < 768 ? 20 : 0; // Small offset on mobile
window.scrollTo({
top: elementTop - offset,
behavior: 'smooth'
});
}
};
const handleRunScript = (scriptPath: string, scriptName: string, mode?: 'local' | 'ssh', server?: any) => { const handleRunScript = (scriptPath: string, scriptName: string, mode?: 'local' | 'ssh', server?: any) => {
setRunningScript({ path: scriptPath, name: scriptName, mode, server }); setRunningScript({ path: scriptPath, name: scriptName, mode, server });
// Scroll to terminal after a short delay to ensure it's rendered
setTimeout(scrollToTerminal, 100);
}; };
const handleCloseTerminal = () => { const handleCloseTerminal = () => {
@@ -102,7 +118,7 @@ export default function Home() {
{/* Running Script Terminal */} {/* Running Script Terminal */}
{runningScript && ( {runningScript && (
<div className="mb-8"> <div ref={terminalRef} className="mb-8">
<Terminal <Terminal
scriptPath={runningScript.path} scriptPath={runningScript.path}
onClose={handleCloseTerminal} onClose={handleCloseTerminal}
@@ -118,7 +134,7 @@ export default function Home() {
)} )}
{activeTab === 'downloaded' && ( {activeTab === 'downloaded' && (
<DownloadedScriptsTab /> <DownloadedScriptsTab onInstallScript={handleRunScript} />
)} )}
{activeTab === 'installed' && ( {activeTab === 'installed' && (

View File

@@ -128,7 +128,7 @@
/* Terminal-specific styles for ANSI escape code rendering */ /* Terminal-specific styles for ANSI escape code rendering */
.terminal-output { .terminal-output {
font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
line-height: 1.2; line-height: 1.2;
} }
@@ -142,6 +142,25 @@
background-color: inherit; background-color: inherit;
} }
/* Enhanced terminal styling */
.xterm {
padding: 0.5rem;
}
/* Set basic background - let ANSI colors work naturally */
.xterm .xterm-viewport {
background-color: #0d1117;
}
.xterm .xterm-screen {
background-color: #0d1117;
}
/* Better selection colors */
.xterm .xterm-selection {
background-color: #264f78;
}
/* Mobile-specific improvements */ /* Mobile-specific improvements */
@media (max-width: 640px) { @media (max-width: 640px) {
/* Improve touch targets */ /* Improve touch targets */

View File

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