Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2b74c8db8 | ||
|
|
4ed3e42148 | ||
|
|
a09f331d5f | ||
|
|
36beb427c0 | ||
|
|
ca2cbd5a7f | ||
|
|
d6803b99a6 |
43
scripts/ct/debian.sh
Normal file
43
scripts/ct/debian.sh
Normal 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}"
|
||||||
24
scripts/install/debian-install.sh
Normal file
24
scripts/install/debian-install.sh
Normal 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"
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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' && (
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user