feat: Remove executable check and add logo support for local scripts

- Remove executable check from ScriptsList component
- All scripts now show as runnable regardless of permissions
- Add logo support using JSON data from script metadata
- Update ScriptInfo interface to include logo and slug fields
- Modify getCtScripts to fetch logo from corresponding JSON files
- Update ScriptsList UI to display logos with fallback to file icons
- Fix TypeScript errors for proper type safety
This commit is contained in:
Michel Roegl-Brunner
2025-09-10 15:13:08 +02:00
parent 7258f7bcfd
commit 2e743490c9
3 changed files with 40 additions and 12 deletions

View File

@@ -88,16 +88,30 @@ export function ScriptsList({ onRunScript }: ScriptsListProps) {
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<span className="text-2xl">{getFileIcon(script.extension)}</span>
{script.logo ? (
<img
src={script.logo}
alt={`${script.name} logo`}
className="w-8 h-8 rounded object-contain"
onError={(e) => {
// Fallback to file icon if logo fails to load
e.currentTarget.style.display = 'none';
const nextElement = e.currentTarget.nextElementSibling as HTMLElement;
if (nextElement) {
nextElement.style.display = 'block';
}
}}
/>
) : null}
<span className="text-2xl" style={{ display: script.logo ? 'none' : 'block' }}>
{getFileIcon(script.extension)}
</span>
<div>
<h3 className="text-lg font-semibold text-gray-800">{script.name}</h3>
<div className="text-sm text-gray-500 space-y-1">
<p>Size: {formatFileSize(script.size)}</p>
<p>Modified: {formatDate(script.lastModified)}</p>
<p>Extension: {script.extension}</p>
<p className={`font-medium ${script.executable ? 'text-green-600' : 'text-red-600'}`}>
{script.executable ? '✅ Executable' : '❌ Not executable'}
</p>
</div>
</div>
</div>
@@ -111,14 +125,9 @@ export function ScriptsList({ onRunScript }: ScriptsListProps) {
</button>
<button
onClick={() => onRunScript(`scripts/ct/${script.name}`, script.name)}
disabled={!script.executable}
className={`px-4 py-2 text-sm font-medium rounded transition-colors ${
script.executable
? 'bg-green-600 text-white hover:bg-green-700'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
className="px-4 py-2 text-sm font-medium rounded transition-colors bg-green-600 text-white hover:bg-green-700"
>
{script.executable ? '▶️ Run' : '🚫 Cannot Run'}
Run
</button>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import { readdir, stat, access } from 'fs/promises';
import { join, resolve, extname } from 'path';
import { env } from '~/env.js';
import { spawn, ChildProcess } from 'child_process';
import { localScriptsService } from '~/server/services/localScripts';
export interface ScriptInfo {
name: string;
@@ -10,6 +11,8 @@ export interface ScriptInfo {
size: number;
lastModified: Date;
executable: boolean;
logo?: string;
slug?: string;
}
export class ScriptManager {
@@ -85,13 +88,28 @@ export class ScriptManager {
// Check if file is executable
const executable = await this.isExecutable(filePath);
// Extract slug from filename (remove .sh extension)
const slug = file.replace(/\.sh$/, '');
// Try to get logo from JSON data
let logo: string | undefined;
try {
const scriptData = await localScriptsService.getScriptBySlug(slug);
logo = scriptData?.logo || undefined;
} catch (error) {
// JSON file might not exist, that's okay
console.log(`No JSON data found for ${slug}:`, error);
}
scripts.push({
name: file,
path: filePath,
extension,
size: stats.size,
lastModified: stats.mtime,
executable
executable,
logo,
slug
});
}
}

View File

@@ -49,6 +49,7 @@ export class ScriptDownloaderService {
const newPattern = 'SCRIPT_DIR="$(dirname "$0")" \nsource "$SCRIPT_DIR/../core/build.func"';
return content.replace(oldPattern, newPattern);
}
async loadScript(script: Script): Promise<{ success: boolean; message: string; files: string[] }> {