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:
@@ -88,16 +88,30 @@ export function ScriptsList({ onRunScript }: ScriptsListProps) {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-3">
|
<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>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-800">{script.name}</h3>
|
<h3 className="text-lg font-semibold text-gray-800">{script.name}</h3>
|
||||||
<div className="text-sm text-gray-500 space-y-1">
|
<div className="text-sm text-gray-500 space-y-1">
|
||||||
<p>Size: {formatFileSize(script.size)}</p>
|
<p>Size: {formatFileSize(script.size)}</p>
|
||||||
<p>Modified: {formatDate(script.lastModified)}</p>
|
<p>Modified: {formatDate(script.lastModified)}</p>
|
||||||
<p>Extension: {script.extension}</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,14 +125,9 @@ export function ScriptsList({ onRunScript }: ScriptsListProps) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onRunScript(`scripts/ct/${script.name}`, script.name)}
|
onClick={() => onRunScript(`scripts/ct/${script.name}`, script.name)}
|
||||||
disabled={!script.executable}
|
className="px-4 py-2 text-sm font-medium rounded transition-colors bg-green-600 text-white hover:bg-green-700"
|
||||||
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'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{script.executable ? '▶️ Run' : '🚫 Cannot Run'}
|
▶️ Run
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { readdir, stat, access } from 'fs/promises';
|
|||||||
import { join, resolve, extname } from 'path';
|
import { join, resolve, extname } from 'path';
|
||||||
import { env } from '~/env.js';
|
import { env } from '~/env.js';
|
||||||
import { spawn, ChildProcess } from 'child_process';
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
|
import { localScriptsService } from '~/server/services/localScripts';
|
||||||
|
|
||||||
export interface ScriptInfo {
|
export interface ScriptInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -10,6 +11,8 @@ export interface ScriptInfo {
|
|||||||
size: number;
|
size: number;
|
||||||
lastModified: Date;
|
lastModified: Date;
|
||||||
executable: boolean;
|
executable: boolean;
|
||||||
|
logo?: string;
|
||||||
|
slug?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScriptManager {
|
export class ScriptManager {
|
||||||
@@ -85,13 +88,28 @@ export class ScriptManager {
|
|||||||
// Check if file is executable
|
// Check if file is executable
|
||||||
const executable = await this.isExecutable(filePath);
|
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({
|
scripts.push({
|
||||||
name: file,
|
name: file,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
extension,
|
extension,
|
||||||
size: stats.size,
|
size: stats.size,
|
||||||
lastModified: stats.mtime,
|
lastModified: stats.mtime,
|
||||||
executable
|
executable,
|
||||||
|
logo,
|
||||||
|
slug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export class ScriptDownloaderService {
|
|||||||
const newPattern = 'SCRIPT_DIR="$(dirname "$0")" \nsource "$SCRIPT_DIR/../core/build.func"';
|
const newPattern = 'SCRIPT_DIR="$(dirname "$0")" \nsource "$SCRIPT_DIR/../core/build.func"';
|
||||||
|
|
||||||
return content.replace(oldPattern, newPattern);
|
return content.replace(oldPattern, newPattern);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadScript(script: Script): Promise<{ success: boolean; message: string; files: string[] }> {
|
async loadScript(script: Script): Promise<{ success: boolean; message: string; files: string[] }> {
|
||||||
|
|||||||
Reference in New Issue
Block a user