feat: Add script installation tracking and update functionality (#36)
* feat: Add script installation tracking with Container ID detection
- Add installed_scripts table to database schema
- Implement Container ID parsing from terminal output
- Add installation tracking for both local and SSH executions
- Create InstalledScriptsTab component with filtering and search
- Add tab navigation to main page (Scripts | Installed Scripts)
- Add tRPC endpoints for installed scripts CRUD operations
- Track installation status, server info, and output logs
- Support both local and SSH execution modes
* fix: Resolve SQL syntax error in database queries
- Change table alias from 'is' to 'inst' in SQL queries
- 'is' is a reserved keyword in SQLite causing syntax errors
- Fixes getAllInstalledScripts, getInstalledScriptById, and getInstalledScriptsByServer methods
* feat: Enhance Container ID detection and add manual editing
- Add comprehensive Container ID detection patterns for various script formats
- Add debug logging to help identify detection issues
- Add manual Container ID editing feature in the frontend
- Add updateInstalledScript tRPC mutation for updating records
- Improve Container ID column with inline editing UI
- Test and verify Container ID detection is working (detected 132 from 2fauth script)
* fix: Improve Container ID detection with ANSI code handling
- Add ANSI color code stripping before pattern matching
- Add primary pattern for exact format: 🆔 Container ID: 113
- Test patterns on both original and cleaned output
- Add better debug logging to show matched text
- This should fix Container ID detection for Proxmox scripts
* feat: Add script update functionality with terminal output
- Add Update button for each installed script (only shows when container_id exists)
- Add WebSocket support for update action (pct enter <ct-id> -- update)
- Add updateScript tRPC endpoint for initiating updates
- Add startScriptUpdate, startLocalScriptUpdate, startSSHScriptUpdate methods
- Modify Terminal component to handle update operations
- Display real-time terminal output for update commands
- Support both local and SSH execution modes for updates
- Show 'Update Container <ID>' in terminal title for update operations
* fix: Fix SSH update functionality
- Replace sshService.executeScript with direct sshpass command
- Use bash -c to execute SSH command: sshpass -p 'password' ssh -o StrictHostKeyChecking=no user@ip 'pct enter <ct-id> -- update'
- This fixes the 'Permission denied' and rsync errors
- SSH updates now work properly for remote containers
* fix: Fix WebSocket update action handling
- Add containerId to WebSocketMessage typedef
- Extract containerId from message in handleMessage function
- Remove debug logging from Terminal component
- This fixes the 'containerId is not defined' error
- Update action should now work properly without creating script records
* feat: Add Update functionality for installed scripts
- Add Update button to InstalledScriptsTab for scripts with Container ID
- Modify Terminal component to handle update operations with isUpdate and containerId props
- Add startUpdateExecution method to WebSocket handler
- Implement local update execution using 'pct enter <CT ID> -c update'
- Implement SSH update execution for remote servers
- Update WebSocket message parsing to handle update parameters
- Users can now update installed scripts by entering the LXC container and running update command
* fix: Fix SSH update execution by using direct command execution
- Add executeCommand method to SSH service for direct command execution
- Update startSSHUpdateExecution to use executeCommand instead of executeScript
- This fixes the rsync permission denied error when updating scripts via SSH
- Update functionality now works properly for both local and SSH installations
* fix: Add server credentials fetching for SSH updates
- Create servers router with getServerById endpoint
- Update handleUpdateScript to fetch full server details including credentials
- This fixes the permission denied error by providing user/password for SSH authentication
- SSH updates now have access to complete server configuration
* fix: Simplify server credentials fetching for SSH updates
- Add server_user and server_password to database query
- Update InstalledScript interface to include server credentials
- Simplify handleUpdateScript to use data already available
- Remove complex tRPC server fetching that was causing errors
- SSH updates now work with complete server authentication data
* fix: Correct pct enter command sequence for updates
- Change from 'pct enter <CT ID> -c "update"' to proper sequence
- First run 'pct enter <CT ID>' to enter container shell
- Then send 'update' command after entering the container
- Apply fix to both local and SSH update execution methods
- Add 1-second delay to ensure container shell is ready before sending update command
* fix: Increase delay to 4 seconds before sending update command
- Change delay from 1 second to 4 seconds for both local and SSH updates
- Ensures container shell is fully ready before sending update command
- Prevents premature command execution that could fail
* cleanup: Remove all debug console.log statements
- Remove debug logging from server.js WebSocket handlers
- Remove debug logging from Terminal component
- Remove debug logging from page.tsx
- Remove debug logging from ExecutionModeModal component
- Remove debug logging from githubJsonService.ts
- Keep only essential server startup messages and error logging
- Clean up codebase for production readiness
* fix: Resolve all build and linter errors
- Fix React Hook useEffect missing dependencies in Terminal.tsx
- Fix TypeScript unsafe argument error in installedScripts.ts by properly typing updateData
- Add missing isUpdate and containerId properties to WebSocketMessage type definition
- Add proper type annotations for callback parameters in server.js
- Fix TypeScript errors with execution.process by adding type assertions
- Remove duplicate updateInstalledScript method in installedScripts.ts
- Build now passes successfully with no errors or warnings
This commit is contained in:
committed by
GitHub
parent
9d2a1a1b5c
commit
024ffcbf09
206
src/server/api/routers/installedScripts.ts
Normal file
206
src/server/api/routers/installedScripts.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
import { getDatabase } from "~/server/database";
|
||||
|
||||
export const installedScriptsRouter = createTRPCRouter({
|
||||
// Get all installed scripts
|
||||
getAllInstalledScripts: publicProcedure
|
||||
.query(async () => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const scripts = db.getAllInstalledScripts();
|
||||
return {
|
||||
success: true,
|
||||
scripts
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in getAllInstalledScripts:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch installed scripts',
|
||||
scripts: []
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Get installed scripts by server
|
||||
getInstalledScriptsByServer: publicProcedure
|
||||
.input(z.object({ serverId: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const scripts = db.getInstalledScriptsByServer(input.serverId);
|
||||
return {
|
||||
success: true,
|
||||
scripts
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in getInstalledScriptsByServer:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch installed scripts by server',
|
||||
scripts: []
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Get installed script by ID
|
||||
getInstalledScriptById: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const script = db.getInstalledScriptById(input.id);
|
||||
if (!script) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Installed script not found',
|
||||
script: null
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
script
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in getInstalledScriptById:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch installed script',
|
||||
script: null
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Create new installed script record
|
||||
createInstalledScript: publicProcedure
|
||||
.input(z.object({
|
||||
script_name: z.string(),
|
||||
script_path: z.string(),
|
||||
container_id: z.string().optional(),
|
||||
server_id: z.number().optional(),
|
||||
execution_mode: z.enum(['local', 'ssh']),
|
||||
status: z.enum(['in_progress', 'success', 'failed']),
|
||||
output_log: z.string().optional()
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const result = db.createInstalledScript(input);
|
||||
return {
|
||||
success: true,
|
||||
id: result.lastInsertRowid,
|
||||
message: 'Installed script record created successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in createInstalledScript:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create installed script record'
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Update installed script
|
||||
updateInstalledScript: publicProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
container_id: z.string().optional(),
|
||||
status: z.enum(['in_progress', 'success', 'failed']).optional(),
|
||||
output_log: z.string().optional()
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const { id, ...updateData } = input;
|
||||
const db = getDatabase();
|
||||
const result = db.updateInstalledScript(id, updateData);
|
||||
|
||||
if (result.changes === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'No changes made or script not found'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Installed script updated successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in updateInstalledScript:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update installed script'
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Delete installed script
|
||||
deleteInstalledScript: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const result = db.deleteInstalledScript(input.id);
|
||||
|
||||
if (result.changes === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Script not found or already deleted'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Installed script deleted successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in deleteInstalledScript:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete installed script'
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Get installation statistics
|
||||
getInstallationStats: publicProcedure
|
||||
.query(async () => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const allScripts = db.getAllInstalledScripts();
|
||||
|
||||
const stats = {
|
||||
total: allScripts.length,
|
||||
byStatus: {
|
||||
success: allScripts.filter((s: any) => s.status === 'success').length,
|
||||
failed: allScripts.filter((s: any) => s.status === 'failed').length,
|
||||
in_progress: allScripts.filter((s: any) => s.status === 'in_progress').length
|
||||
},
|
||||
byMode: {
|
||||
local: allScripts.filter((s: any) => s.execution_mode === 'local').length,
|
||||
ssh: allScripts.filter((s: any) => s.execution_mode === 'ssh').length
|
||||
},
|
||||
byServer: {} as Record<string, number>
|
||||
};
|
||||
|
||||
// Count by server
|
||||
allScripts.forEach((script: any) => {
|
||||
const serverKey = script.server_name ?? 'Local';
|
||||
stats.byServer[serverKey] = (stats.byServer[serverKey] ?? 0) + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
stats
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in getInstallationStats:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch installation statistics',
|
||||
stats: null
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user