Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb5056508d | ||
|
|
b793c57000 | ||
|
|
6b45c41334 | ||
|
|
a8eb41e087 | ||
|
|
52adbd9f5c | ||
|
|
73d3aeec99 | ||
|
|
1635bb17da | ||
|
|
b4b8da5725 |
3
.github/release-drafter.yml
vendored
3
.github/release-drafter.yml
vendored
@@ -7,6 +7,9 @@ exclude-labels:
|
|||||||
- automated
|
- automated
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
|
- title: "Breaking Changes"
|
||||||
|
labels:
|
||||||
|
- breaking
|
||||||
- title: "🚀 Features"
|
- title: "🚀 Features"
|
||||||
labels:
|
labels:
|
||||||
- feature
|
- feature
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -49,4 +49,5 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# idea files
|
# idea files
|
||||||
.idea
|
.idea
|
||||||
|
/generated/prisma
|
||||||
|
|||||||
820
package-lock.json
generated
820
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,12 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@prisma/client": "^6.17.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.8",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tanstack/react-query": "^5.90.3",
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"@trpc/client": "^11.6.0",
|
"@trpc/client": "^11.6.0",
|
||||||
"@trpc/react-query": "^11.6.0",
|
"@trpc/react-query": "^11.6.0",
|
||||||
"@trpc/server": "^11.6.0",
|
"@trpc/server": "^11.6.0",
|
||||||
@@ -36,11 +37,10 @@
|
|||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"better-sqlite3": "^12.4.1",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lucide-react": "^0.545.0",
|
"lucide-react": "^0.546.0",
|
||||||
"next": "^15.5.5",
|
"next": "^15.5.5",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.0.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/better-sqlite3": "^7.6.8",
|
"@types/better-sqlite3": "^7.6.8",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^24.7.2",
|
"@types/node": "^24.8.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^5.0.2",
|
"@vitejs/plugin-react": "^5.0.2",
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.0",
|
"prettier-plugin-tailwindcss": "^0.7.0",
|
||||||
|
"prisma": "^6.17.1",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.46.1",
|
"typescript-eslint": "^8.46.1",
|
||||||
|
|||||||
45
prisma/schema.prisma
Normal file
45
prisma/schema.prisma
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model InstalledScript {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
script_name String
|
||||||
|
script_path String
|
||||||
|
container_id String?
|
||||||
|
server_id Int?
|
||||||
|
execution_mode String
|
||||||
|
installation_date DateTime? @default(now())
|
||||||
|
status String
|
||||||
|
output_log String?
|
||||||
|
web_ui_ip String?
|
||||||
|
web_ui_port Int?
|
||||||
|
server Server? @relation(fields: [server_id], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
|
@@map("installed_scripts")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Server {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String @unique
|
||||||
|
ip String
|
||||||
|
user String
|
||||||
|
password String?
|
||||||
|
auth_type String? @default("password")
|
||||||
|
ssh_key String?
|
||||||
|
ssh_key_passphrase String?
|
||||||
|
ssh_port Int? @default(22)
|
||||||
|
color String?
|
||||||
|
created_at DateTime? @default(now())
|
||||||
|
updated_at DateTime? @updatedAt
|
||||||
|
ssh_key_path String?
|
||||||
|
key_generated Boolean? @default(false)
|
||||||
|
installed_scripts InstalledScript[]
|
||||||
|
|
||||||
|
@@map("servers")
|
||||||
|
}
|
||||||
38
server.js
38
server.js
@@ -7,7 +7,7 @@ import { join, resolve } from 'path';
|
|||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import { spawn as ptySpawn } from 'node-pty';
|
import { spawn as ptySpawn } from 'node-pty';
|
||||||
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
|
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
|
||||||
import { getDatabase } from './src/server/database.js';
|
import { getDatabase } from './src/server/database-prisma.js';
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV !== 'production';
|
const dev = process.env.NODE_ENV !== 'production';
|
||||||
const hostname = '0.0.0.0';
|
const hostname = '0.0.0.0';
|
||||||
@@ -186,11 +186,11 @@ class ScriptExecutionHandler {
|
|||||||
* @param {string} scriptPath - Path to the script
|
* @param {string} scriptPath - Path to the script
|
||||||
* @param {string} executionMode - 'local' or 'ssh'
|
* @param {string} executionMode - 'local' or 'ssh'
|
||||||
* @param {number|null} serverId - Server ID for SSH executions
|
* @param {number|null} serverId - Server ID for SSH executions
|
||||||
* @returns {number|null} - Installation record ID
|
* @returns {Promise<number|null>} - Installation record ID
|
||||||
*/
|
*/
|
||||||
createInstallationRecord(scriptName, scriptPath, executionMode, serverId = null) {
|
async createInstallationRecord(scriptName, scriptPath, executionMode, serverId = null) {
|
||||||
try {
|
try {
|
||||||
const result = this.db.createInstalledScript({
|
const result = await this.db.createInstalledScript({
|
||||||
script_name: scriptName,
|
script_name: scriptName,
|
||||||
script_path: scriptPath,
|
script_path: scriptPath,
|
||||||
container_id: undefined,
|
container_id: undefined,
|
||||||
@@ -199,7 +199,7 @@ class ScriptExecutionHandler {
|
|||||||
status: 'in_progress',
|
status: 'in_progress',
|
||||||
output_log: ''
|
output_log: ''
|
||||||
});
|
});
|
||||||
return Number(result.lastInsertRowid);
|
return Number(result.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating installation record:', error);
|
console.error('Error creating installation record:', error);
|
||||||
return null;
|
return null;
|
||||||
@@ -211,9 +211,9 @@ class ScriptExecutionHandler {
|
|||||||
* @param {number} installationId - Installation record ID
|
* @param {number} installationId - Installation record ID
|
||||||
* @param {Object} updateData - Data to update
|
* @param {Object} updateData - Data to update
|
||||||
*/
|
*/
|
||||||
updateInstallationRecord(installationId, updateData) {
|
async updateInstallationRecord(installationId, updateData) {
|
||||||
try {
|
try {
|
||||||
this.db.updateInstalledScript(installationId, updateData);
|
await this.db.updateInstalledScript(installationId, updateData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating installation record:', error);
|
console.error('Error updating installation record:', error);
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ class ScriptExecutionHandler {
|
|||||||
|
|
||||||
// Create installation record
|
// Create installation record
|
||||||
const serverId = server ? (server.id ?? null) : null;
|
const serverId = server ? (server.id ?? null) : null;
|
||||||
installationId = this.createInstallationRecord(scriptName, scriptPath, mode, serverId);
|
installationId = await this.createInstallationRecord(scriptName, scriptPath, mode, serverId);
|
||||||
|
|
||||||
if (!installationId) {
|
if (!installationId) {
|
||||||
console.error('Failed to create installation record');
|
console.error('Failed to create installation record');
|
||||||
@@ -356,7 +356,7 @@ class ScriptExecutionHandler {
|
|||||||
|
|
||||||
// Update installation record with failure
|
// Update installation record with failure
|
||||||
if (installationId) {
|
if (installationId) {
|
||||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
await this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -394,7 +394,7 @@ class ScriptExecutionHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Handle pty data (both stdout and stderr combined)
|
// Handle pty data (both stdout and stderr combined)
|
||||||
childProcess.onData((data) => {
|
childProcess.onData(async (data) => {
|
||||||
const output = data.toString();
|
const output = data.toString();
|
||||||
|
|
||||||
// Store output in buffer for logging
|
// Store output in buffer for logging
|
||||||
@@ -410,7 +410,7 @@ class ScriptExecutionHandler {
|
|||||||
// Parse for Container ID
|
// Parse for Container ID
|
||||||
const containerId = this.parseContainerId(output);
|
const containerId = this.parseContainerId(output);
|
||||||
if (containerId && installationId) {
|
if (containerId && installationId) {
|
||||||
this.updateInstallationRecord(installationId, { container_id: containerId });
|
await this.updateInstallationRecord(installationId, { container_id: containerId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse for Web UI URL
|
// Parse for Web UI URL
|
||||||
@@ -418,7 +418,7 @@ class ScriptExecutionHandler {
|
|||||||
if (webUIUrl && installationId) {
|
if (webUIUrl && installationId) {
|
||||||
const { ip, port } = webUIUrl;
|
const { ip, port } = webUIUrl;
|
||||||
if (ip && port) {
|
if (ip && port) {
|
||||||
this.updateInstallationRecord(installationId, {
|
await this.updateInstallationRecord(installationId, {
|
||||||
web_ui_ip: ip,
|
web_ui_ip: ip,
|
||||||
web_ui_port: port
|
web_ui_port: port
|
||||||
});
|
});
|
||||||
@@ -464,7 +464,7 @@ class ScriptExecutionHandler {
|
|||||||
|
|
||||||
// Update installation record with failure
|
// Update installation record with failure
|
||||||
if (installationId) {
|
if (installationId) {
|
||||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
await this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,7 +491,7 @@ class ScriptExecutionHandler {
|
|||||||
const execution = /** @type {ExecutionResult} */ (await sshService.executeScript(
|
const execution = /** @type {ExecutionResult} */ (await sshService.executeScript(
|
||||||
server,
|
server,
|
||||||
scriptPath,
|
scriptPath,
|
||||||
/** @param {string} data */ (data) => {
|
/** @param {string} data */ async (data) => {
|
||||||
// Store output in buffer for logging
|
// Store output in buffer for logging
|
||||||
const exec = this.activeExecutions.get(executionId);
|
const exec = this.activeExecutions.get(executionId);
|
||||||
if (exec) {
|
if (exec) {
|
||||||
@@ -505,7 +505,7 @@ class ScriptExecutionHandler {
|
|||||||
// Parse for Container ID
|
// Parse for Container ID
|
||||||
const containerId = this.parseContainerId(data);
|
const containerId = this.parseContainerId(data);
|
||||||
if (containerId && installationId) {
|
if (containerId && installationId) {
|
||||||
this.updateInstallationRecord(installationId, { container_id: containerId });
|
await this.updateInstallationRecord(installationId, { container_id: containerId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse for Web UI URL
|
// Parse for Web UI URL
|
||||||
@@ -513,7 +513,7 @@ class ScriptExecutionHandler {
|
|||||||
if (webUIUrl && installationId) {
|
if (webUIUrl && installationId) {
|
||||||
const { ip, port } = webUIUrl;
|
const { ip, port } = webUIUrl;
|
||||||
if (ip && port) {
|
if (ip && port) {
|
||||||
this.updateInstallationRecord(installationId, {
|
await this.updateInstallationRecord(installationId, {
|
||||||
web_ui_ip: ip,
|
web_ui_ip: ip,
|
||||||
web_ui_port: port
|
web_ui_port: port
|
||||||
});
|
});
|
||||||
@@ -545,13 +545,13 @@ class ScriptExecutionHandler {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** @param {number} code */ (code) => {
|
/** @param {number} code */ async (code) => {
|
||||||
const exec = this.activeExecutions.get(executionId);
|
const exec = this.activeExecutions.get(executionId);
|
||||||
const isSuccess = code === 0;
|
const isSuccess = code === 0;
|
||||||
|
|
||||||
// Update installation record with final status and output
|
// Update installation record with final status and output
|
||||||
if (installationId && exec) {
|
if (installationId && exec) {
|
||||||
this.updateInstallationRecord(installationId, {
|
await this.updateInstallationRecord(installationId, {
|
||||||
status: isSuccess ? 'success' : 'failed',
|
status: isSuccess ? 'success' : 'failed',
|
||||||
output_log: exec.outputBuffer
|
output_log: exec.outputBuffer
|
||||||
});
|
});
|
||||||
@@ -586,7 +586,7 @@ class ScriptExecutionHandler {
|
|||||||
|
|
||||||
// Update installation record with failure
|
// Update installation record with failure
|
||||||
if (installationId) {
|
if (installationId) {
|
||||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
await this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
|
|||||||
...prev,
|
...prev,
|
||||||
ssh_key: data.privateKey ?? '',
|
ssh_key: data.privateKey ?? '',
|
||||||
ssh_key_path: keyPath,
|
ssh_key_path: keyPath,
|
||||||
key_generated: 1
|
key_generated: true
|
||||||
}));
|
}));
|
||||||
setGeneratedPublicKey(data.publicKey ?? '');
|
setGeneratedPublicKey(data.publicKey ?? '');
|
||||||
setGeneratedServerId(serverId);
|
setGeneratedServerId(serverId);
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ export function ServerList({ servers, onUpdate, onDelete }: ServerListProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-xs text-muted-foreground">
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
Created: {new Date(server.created_at).toLocaleDateString()}
|
Created: {server.created_at ? new Date(server.created_at).toLocaleDateString() : 'Unknown'}
|
||||||
{server.updated_at !== server.created_at && (
|
{server.updated_at && server.updated_at !== server.created_at && (
|
||||||
<span> • Updated: {new Date(server.updated_at).toLocaleDateString()}</span>
|
<span> • Updated: {new Date(server.updated_at).toLocaleDateString()}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -253,7 +253,7 @@ export function ServerList({ servers, onUpdate, onDelete }: ServerListProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{/* View Public Key button - only show for generated keys */}
|
{/* View Public Key button - only show for generated keys */}
|
||||||
{server.key_generated === 1 && (
|
{server.key_generated === true && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleViewPublicKey(server)}
|
onClick={() => handleViewPublicKey(server)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getDatabase } from '../../../../../server/database';
|
import { getDatabase } from '../../../../../server/database-prisma.js';
|
||||||
import { getSSHService } from '../../../../../server/ssh-service';
|
import { getSSHService } from '../../../../../server/ssh-service';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
@@ -18,7 +18,7 @@ export async function GET(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const server = db.getServerById(id);
|
const server = await db.getServerById(id);
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getDatabase } from '../../../../server/database';
|
import { getDatabase } from '../../../../server/database-prisma.js';
|
||||||
import type { CreateServerData } from '../../../../types/server';
|
import type { CreateServerData } from '../../../../types/server';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
@@ -18,7 +18,7 @@ export async function GET(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const server = db.getServerById(id);
|
const server = await db.getServerById(id);
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -95,7 +95,7 @@ export async function PUT(
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
// Check if server exists
|
// Check if server exists
|
||||||
const existingServer = db.getServerById(id);
|
const existingServer = await db.getServerById(id);
|
||||||
if (!existingServer) {
|
if (!existingServer) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Server not found' },
|
{ error: 'Server not found' },
|
||||||
@@ -103,7 +103,7 @@ export async function PUT(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = db.updateServer(id, {
|
await db.updateServer(id, {
|
||||||
name,
|
name,
|
||||||
ip,
|
ip,
|
||||||
user,
|
user,
|
||||||
@@ -113,14 +113,14 @@ export async function PUT(
|
|||||||
ssh_key_passphrase,
|
ssh_key_passphrase,
|
||||||
ssh_port: ssh_port ?? 22,
|
ssh_port: ssh_port ?? 22,
|
||||||
color,
|
color,
|
||||||
key_generated: key_generated ?? 0,
|
key_generated: key_generated ?? false,
|
||||||
ssh_key_path
|
ssh_key_path
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: 'Server updated successfully',
|
message: 'Server updated successfully',
|
||||||
changes: result.changes
|
changes: 1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -158,7 +158,7 @@ export async function DELETE(
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
// Check if server exists
|
// Check if server exists
|
||||||
const existingServer = db.getServerById(id);
|
const existingServer = await db.getServerById(id);
|
||||||
if (!existingServer) {
|
if (!existingServer) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Server not found' },
|
{ error: 'Server not found' },
|
||||||
@@ -167,14 +167,14 @@ export async function DELETE(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete all installed scripts associated with this server
|
// Delete all installed scripts associated with this server
|
||||||
db.deleteInstalledScriptsByServer(id);
|
await db.deleteInstalledScriptsByServer(id);
|
||||||
|
|
||||||
const result = db.deleteServer(id);
|
await db.deleteServer(id);
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: 'Server deleted successfully',
|
message: 'Server deleted successfully',
|
||||||
changes: result.changes
|
changes: 1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getDatabase } from '../../../../../server/database';
|
import { getDatabase } from '../../../../../server/database-prisma.js';
|
||||||
import { getSSHService } from '../../../../../server/ssh-service';
|
import { getSSHService } from '../../../../../server/ssh-service';
|
||||||
import type { Server } from '../../../../../types/server';
|
import type { Server } from '../../../../../types/server';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export async function POST(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const server = db.getServerById(id) as Server;
|
const server = await db.getServerById(id) as Server;
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getSSHService } from '../../../../server/ssh-service';
|
import { getSSHService } from '../../../../server/ssh-service';
|
||||||
import { getDatabase } from '../../../../server/database';
|
import { getDatabase } from '../../../../server/database-prisma.js';
|
||||||
|
|
||||||
export async function POST(_request: NextRequest) {
|
export async function POST(_request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -9,7 +9,7 @@ export async function POST(_request: NextRequest) {
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
// Get the next available server ID for key file naming
|
// Get the next available server ID for key file naming
|
||||||
const serverId = db.getNextServerId();
|
const serverId = await db.getNextServerId();
|
||||||
|
|
||||||
const keyPair = await sshService.generateKeyPair(serverId);
|
const keyPair = await sshService.generateKeyPair(serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getDatabase } from '../../../server/database';
|
import { getDatabase } from '../../../server/database-prisma.js';
|
||||||
import type { CreateServerData } from '../../../types/server';
|
import type { CreateServerData } from '../../../types/server';
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const servers = db.getAllServers();
|
const servers = await db.getAllServers();
|
||||||
return NextResponse.json(servers);
|
return NextResponse.json(servers);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching servers:', error);
|
console.error('Error fetching servers:', error);
|
||||||
@@ -61,7 +61,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const result = db.createServer({
|
const result = await db.createServer({
|
||||||
name,
|
name,
|
||||||
ip,
|
ip,
|
||||||
user,
|
user,
|
||||||
@@ -71,14 +71,14 @@ export async function POST(request: NextRequest) {
|
|||||||
ssh_key_passphrase,
|
ssh_key_passphrase,
|
||||||
ssh_port: ssh_port ?? 22,
|
ssh_port: ssh_port ?? 22,
|
||||||
color,
|
color,
|
||||||
key_generated: key_generated ?? 0,
|
key_generated: key_generated ?? false,
|
||||||
ssh_key_path
|
ssh_key_path
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: 'Server created successfully',
|
message: 'Server created successfully',
|
||||||
id: result.lastInsertRowid
|
id: result.id
|
||||||
},
|
},
|
||||||
{ status: 201 }
|
{ status: 201 }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||||
import { getDatabase } from "~/server/database";
|
import { getDatabase } from "~/server/database-prisma.js";
|
||||||
// Removed unused imports
|
// Removed unused imports
|
||||||
|
|
||||||
|
|
||||||
@@ -10,10 +10,26 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.query(async () => {
|
.query(async () => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const scripts = db.getAllInstalledScripts();
|
const scripts = await db.getAllInstalledScripts();
|
||||||
|
|
||||||
|
// Transform scripts to flatten server data for frontend compatibility
|
||||||
|
const transformedScripts = scripts.map(script => ({
|
||||||
|
...script,
|
||||||
|
server_name: script.server?.name ?? null,
|
||||||
|
server_ip: script.server?.ip ?? null,
|
||||||
|
server_user: script.server?.user ?? null,
|
||||||
|
server_password: script.server?.password ?? null,
|
||||||
|
server_auth_type: script.server?.auth_type ?? null,
|
||||||
|
server_ssh_key: script.server?.ssh_key ?? null,
|
||||||
|
server_ssh_key_passphrase: script.server?.ssh_key_passphrase ?? null,
|
||||||
|
server_ssh_port: script.server?.ssh_port ?? null,
|
||||||
|
server_color: script.server?.color ?? null,
|
||||||
|
server: undefined // Remove nested server object
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
scripts
|
scripts: transformedScripts
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in getAllInstalledScripts:', error);
|
console.error('Error in getAllInstalledScripts:', error);
|
||||||
@@ -31,10 +47,26 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const scripts = db.getInstalledScriptsByServer(input.serverId);
|
const scripts = await db.getInstalledScriptsByServer(input.serverId);
|
||||||
|
|
||||||
|
// Transform scripts to flatten server data for frontend compatibility
|
||||||
|
const transformedScripts = scripts.map(script => ({
|
||||||
|
...script,
|
||||||
|
server_name: script.server?.name ?? null,
|
||||||
|
server_ip: script.server?.ip ?? null,
|
||||||
|
server_user: script.server?.user ?? null,
|
||||||
|
server_password: script.server?.password ?? null,
|
||||||
|
server_auth_type: script.server?.auth_type ?? null,
|
||||||
|
server_ssh_key: script.server?.ssh_key ?? null,
|
||||||
|
server_ssh_key_passphrase: script.server?.ssh_key_passphrase ?? null,
|
||||||
|
server_ssh_port: script.server?.ssh_port ?? null,
|
||||||
|
server_color: script.server?.color ?? null,
|
||||||
|
server: undefined // Remove nested server object
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
scripts
|
scripts: transformedScripts
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in getInstalledScriptsByServer:', error);
|
console.error('Error in getInstalledScriptsByServer:', error);
|
||||||
@@ -52,7 +84,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const script = db.getInstalledScriptById(input.id);
|
const script = await db.getInstalledScriptById(input.id);
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -60,9 +92,24 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
script: null
|
script: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Transform script to flatten server data for frontend compatibility
|
||||||
|
const transformedScript = {
|
||||||
|
...script,
|
||||||
|
server_name: script.server?.name ?? null,
|
||||||
|
server_ip: script.server?.ip ?? null,
|
||||||
|
server_user: script.server?.user ?? null,
|
||||||
|
server_password: script.server?.password ?? null,
|
||||||
|
server_auth_type: script.server?.auth_type ?? null,
|
||||||
|
server_ssh_key: script.server?.ssh_key ?? null,
|
||||||
|
server_ssh_key_passphrase: script.server?.ssh_key_passphrase ?? null,
|
||||||
|
server_ssh_port: script.server?.ssh_port ?? null,
|
||||||
|
server_color: script.server?.color ?? null,
|
||||||
|
server: undefined // Remove nested server object
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
script
|
script: transformedScript
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in getInstalledScriptById:', error);
|
console.error('Error in getInstalledScriptById:', error);
|
||||||
@@ -90,10 +137,10 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const result = db.createInstalledScript(input);
|
const result = await db.createInstalledScript(input);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
id: result.lastInsertRowid,
|
id: result.id,
|
||||||
message: 'Installed script record created successfully'
|
message: 'Installed script record created successfully'
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -120,9 +167,9 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
try {
|
try {
|
||||||
const { id, ...updateData } = input;
|
const { id, ...updateData } = input;
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const result = db.updateInstalledScript(id, updateData);
|
const result = await db.updateInstalledScript(id, updateData);
|
||||||
|
|
||||||
if (result.changes === 0) {
|
if (!result) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'No changes made or script not found'
|
error: 'No changes made or script not found'
|
||||||
@@ -148,9 +195,9 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const result = db.deleteInstalledScript(input.id);
|
const result = await db.deleteInstalledScript(input.id);
|
||||||
|
|
||||||
if (result.changes === 0) {
|
if (!result) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Script not found or already deleted'
|
error: 'Script not found or already deleted'
|
||||||
@@ -175,7 +222,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.query(async () => {
|
.query(async () => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const allScripts = db.getAllInstalledScripts();
|
const allScripts = await db.getAllInstalledScripts();
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
total: allScripts.length,
|
total: allScripts.length,
|
||||||
@@ -219,7 +266,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const server = db.getServerById(input.serverId);
|
const server = await db.getServerById(input.serverId);
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
console.error('Server not found for ID:', input.serverId);
|
console.error('Server not found for ID:', input.serverId);
|
||||||
@@ -350,7 +397,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
|
|
||||||
|
|
||||||
// Get existing scripts to check for duplicates
|
// Get existing scripts to check for duplicates
|
||||||
const existingScripts = db.getAllInstalledScripts();
|
const existingScripts = await db.getAllInstalledScripts();
|
||||||
|
|
||||||
// Create installed script records for detected containers (skip duplicates)
|
// Create installed script records for detected containers (skip duplicates)
|
||||||
const createdScripts = [];
|
const createdScripts = [];
|
||||||
@@ -373,7 +420,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = db.createInstalledScript({
|
const result = await db.createInstalledScript({
|
||||||
script_name: container.hostname,
|
script_name: container.hostname,
|
||||||
script_path: `detected/${container.hostname}`,
|
script_path: `detected/${container.hostname}`,
|
||||||
container_id: container.containerId,
|
container_id: container.containerId,
|
||||||
@@ -384,7 +431,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
createdScripts.push({
|
createdScripts.push({
|
||||||
id: result.lastInsertRowid,
|
id: result.id,
|
||||||
containerId: container.containerId,
|
containerId: container.containerId,
|
||||||
hostname: container.hostname,
|
hostname: container.hostname,
|
||||||
serverName: container.serverName
|
serverName: container.serverName
|
||||||
@@ -420,8 +467,8 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const allScripts = db.getAllInstalledScripts();
|
const allScripts = await db.getAllInstalledScripts();
|
||||||
const allServers = db.getAllServers();
|
const allServers = await db.getAllServers();
|
||||||
|
|
||||||
|
|
||||||
if (allScripts.length === 0) {
|
if (allScripts.length === 0) {
|
||||||
@@ -452,7 +499,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
const scriptData = script as any;
|
const scriptData = script as any;
|
||||||
const server = allServers.find((s: any) => s.id === scriptData.server_id);
|
const server = allServers.find((s: any) => s.id === scriptData.server_id);
|
||||||
if (!server) {
|
if (!server) {
|
||||||
db.deleteInstalledScript(Number(scriptData.id));
|
await db.deleteInstalledScript(Number(scriptData.id));
|
||||||
deletedScripts.push(String(scriptData.script_name));
|
deletedScripts.push(String(scriptData.script_name));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -488,7 +535,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!containerExists) {
|
if (!containerExists) {
|
||||||
db.deleteInstalledScript(Number(scriptData.id));
|
await db.deleteInstalledScript(Number(scriptData.id));
|
||||||
deletedScripts.push(String(scriptData.script_name));
|
deletedScripts.push(String(scriptData.script_name));
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
@@ -525,7 +572,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const allServers = db.getAllServers();
|
const allServers = await db.getAllServers();
|
||||||
const statusMap: Record<string, 'running' | 'stopped' | 'unknown'> = {};
|
const statusMap: Record<string, 'running' | 'stopped' | 'unknown'> = {};
|
||||||
|
|
||||||
// Import SSH services
|
// Import SSH services
|
||||||
@@ -630,7 +677,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const script = db.getInstalledScriptById(input.id);
|
const script = await db.getInstalledScriptById(input.id);
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return {
|
return {
|
||||||
@@ -652,7 +699,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get server info
|
// Get server info
|
||||||
const server = db.getServerById(Number(scriptData.server_id));
|
const server = await db.getServerById(Number(scriptData.server_id));
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -732,7 +779,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const script = db.getInstalledScriptById(input.id);
|
const script = await db.getInstalledScriptById(input.id);
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return {
|
return {
|
||||||
@@ -752,7 +799,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get server info
|
// Get server info
|
||||||
const server = db.getServerById(Number(scriptData.server_id));
|
const server = await db.getServerById(Number(scriptData.server_id));
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -823,7 +870,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const script = db.getInstalledScriptById(input.id);
|
const script = await db.getInstalledScriptById(input.id);
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return {
|
return {
|
||||||
@@ -843,7 +890,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get server info
|
// Get server info
|
||||||
const server = db.getServerById(Number(scriptData.server_id));
|
const server = await db.getServerById(Number(scriptData.server_id));
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -950,9 +997,9 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// If destroy was successful, delete the database record
|
// If destroy was successful, delete the database record
|
||||||
const deleteResult = db.deleteInstalledScript(input.id);
|
const deleteResult = await db.deleteInstalledScript(input.id);
|
||||||
|
|
||||||
if (deleteResult.changes === 0) {
|
if (!deleteResult) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Container destroyed but failed to delete database record'
|
error: 'Container destroyed but failed to delete database record'
|
||||||
@@ -985,7 +1032,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
try {
|
try {
|
||||||
console.log('🔍 Auto-detect WebUI called with id:', input.id);
|
console.log('🔍 Auto-detect WebUI called with id:', input.id);
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const script = db.getInstalledScriptById(input.id);
|
const script = await db.getInstalledScriptById(input.id);
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
console.log('❌ Script not found for id:', input.id);
|
console.log('❌ Script not found for id:', input.id);
|
||||||
@@ -1013,7 +1060,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get server info
|
// Get server info
|
||||||
const server = db.getServerById(Number(scriptData.server_id));
|
const server = await db.getServerById(Number(scriptData.server_id));
|
||||||
if (!server) {
|
if (!server) {
|
||||||
console.log('❌ Server not found for id:', scriptData.server_id);
|
console.log('❌ Server not found for id:', scriptData.server_id);
|
||||||
return {
|
return {
|
||||||
@@ -1121,12 +1168,12 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
|
|
||||||
// Update the database with detected IP and port
|
// Update the database with detected IP and port
|
||||||
console.log('💾 Updating database with IP:', detectedIp, 'Port:', detectedPort);
|
console.log('💾 Updating database with IP:', detectedIp, 'Port:', detectedPort);
|
||||||
const updateResult = db.updateInstalledScript(input.id, {
|
const updateResult = await db.updateInstalledScript(input.id, {
|
||||||
web_ui_ip: detectedIp,
|
web_ui_ip: detectedIp,
|
||||||
web_ui_port: detectedPort
|
web_ui_port: detectedPort
|
||||||
});
|
});
|
||||||
|
|
||||||
if (updateResult.changes === 0) {
|
if (!updateResult) {
|
||||||
console.log('❌ Database update failed - no changes made');
|
console.log('❌ Database update failed - no changes made');
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||||
import { getDatabase } from "~/server/database";
|
import { getDatabase } from "~/server/database-prisma.js";
|
||||||
|
|
||||||
export const serversRouter = createTRPCRouter({
|
export const serversRouter = createTRPCRouter({
|
||||||
getAllServers: publicProcedure
|
getAllServers: publicProcedure
|
||||||
.query(async () => {
|
.query(async () => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const servers = db.getAllServers();
|
const servers = await db.getAllServers();
|
||||||
return { success: true, servers };
|
return { success: true, servers };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching servers:', error);
|
console.error('Error fetching servers:', error);
|
||||||
@@ -24,7 +24,7 @@ export const serversRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const server = db.getServerById(input.id);
|
const server = await db.getServerById(input.id);
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return { success: false, error: 'Server not found', server: null };
|
return { success: false, error: 'Server not found', server: null };
|
||||||
}
|
}
|
||||||
|
|||||||
254
src/server/database-prisma.js
Normal file
254
src/server/database-prisma.js
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
import { prisma } from './db.js';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { writeFileSync, unlinkSync, chmodSync, mkdirSync } from 'fs';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
class DatabaseServicePrisma {
|
||||||
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Ensure data/ssh-keys directory exists
|
||||||
|
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
||||||
|
if (!existsSync(sshKeysDir)) {
|
||||||
|
mkdirSync(sshKeysDir, { mode: 0o700 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server CRUD operations
|
||||||
|
async createServer(serverData) {
|
||||||
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
|
||||||
|
let ssh_key_path = null;
|
||||||
|
|
||||||
|
// If using SSH key authentication, create persistent key file
|
||||||
|
if (auth_type === 'key' && ssh_key) {
|
||||||
|
const serverId = await this.getNextServerId();
|
||||||
|
ssh_key_path = this.createSSHKeyFile(serverId, ssh_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
ip,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
auth_type: auth_type ?? 'password',
|
||||||
|
ssh_key,
|
||||||
|
ssh_key_passphrase,
|
||||||
|
ssh_port: ssh_port ?? 22,
|
||||||
|
ssh_key_path,
|
||||||
|
key_generated: Boolean(key_generated),
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllServers() {
|
||||||
|
return await prisma.server.findMany({
|
||||||
|
orderBy: { created_at: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServerById(id) {
|
||||||
|
return await prisma.server.findUnique({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateServer(id, serverData) {
|
||||||
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
|
||||||
|
// Get existing server to check for key changes
|
||||||
|
const existingServer = await this.getServerById(id);
|
||||||
|
let ssh_key_path = existingServer?.ssh_key_path;
|
||||||
|
|
||||||
|
// Handle SSH key changes
|
||||||
|
if (auth_type === 'key' && ssh_key) {
|
||||||
|
// Delete old key file if it exists
|
||||||
|
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(existingServer.ssh_key_path);
|
||||||
|
// Also delete public key file if it exists
|
||||||
|
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete old SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new key file
|
||||||
|
ssh_key_path = this.createSSHKeyFile(id, ssh_key);
|
||||||
|
} else if (auth_type !== 'key') {
|
||||||
|
// If switching away from key auth, delete key files
|
||||||
|
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(existingServer.ssh_key_path);
|
||||||
|
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ssh_key_path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
ip,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
auth_type: auth_type ?? 'password',
|
||||||
|
ssh_key,
|
||||||
|
ssh_key_passphrase,
|
||||||
|
ssh_port: ssh_port ?? 22,
|
||||||
|
ssh_key_path,
|
||||||
|
key_generated: key_generated !== undefined ? Boolean(key_generated) : (existingServer?.key_generated ?? false),
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteServer(id) {
|
||||||
|
// Get server info before deletion to clean up key files
|
||||||
|
const server = await this.getServerById(id);
|
||||||
|
|
||||||
|
// Delete SSH key files if they exist
|
||||||
|
if (server?.ssh_key_path && existsSync(server.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(server.ssh_key_path);
|
||||||
|
const pubKeyPath = server.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installed Scripts CRUD operations
|
||||||
|
async createInstalledScript(scriptData) {
|
||||||
|
const { script_name, script_path, container_id, server_id, execution_mode, status, output_log, web_ui_ip, web_ui_port } = scriptData;
|
||||||
|
|
||||||
|
return await prisma.installedScript.create({
|
||||||
|
data: {
|
||||||
|
script_name,
|
||||||
|
script_path,
|
||||||
|
container_id: container_id ?? null,
|
||||||
|
server_id: server_id ?? null,
|
||||||
|
execution_mode,
|
||||||
|
status,
|
||||||
|
output_log: output_log ?? null,
|
||||||
|
web_ui_ip: web_ui_ip ?? null,
|
||||||
|
web_ui_port: web_ui_port ?? null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllInstalledScripts() {
|
||||||
|
return await prisma.installedScript.findMany({
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
},
|
||||||
|
orderBy: { installation_date: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstalledScriptById(id) {
|
||||||
|
return await prisma.installedScript.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstalledScriptsByServer(server_id) {
|
||||||
|
return await prisma.installedScript.findMany({
|
||||||
|
where: { server_id },
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
},
|
||||||
|
orderBy: { installation_date: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateInstalledScript(id, updateData) {
|
||||||
|
const { script_name, container_id, status, output_log, web_ui_ip, web_ui_port } = updateData;
|
||||||
|
|
||||||
|
const updateFields = {};
|
||||||
|
if (script_name !== undefined) updateFields.script_name = script_name;
|
||||||
|
if (container_id !== undefined) updateFields.container_id = container_id;
|
||||||
|
if (status !== undefined) updateFields.status = status;
|
||||||
|
if (output_log !== undefined) updateFields.output_log = output_log;
|
||||||
|
if (web_ui_ip !== undefined) updateFields.web_ui_ip = web_ui_ip;
|
||||||
|
if (web_ui_port !== undefined) updateFields.web_ui_port = web_ui_port;
|
||||||
|
|
||||||
|
if (Object.keys(updateFields).length === 0) {
|
||||||
|
return { changes: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.installedScript.update({
|
||||||
|
where: { id },
|
||||||
|
data: updateFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInstalledScript(id) {
|
||||||
|
return await prisma.installedScript.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInstalledScriptsByServer(server_id) {
|
||||||
|
return await prisma.installedScript.deleteMany({
|
||||||
|
where: { server_id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNextServerId() {
|
||||||
|
const result = await prisma.server.findFirst({
|
||||||
|
orderBy: { id: 'desc' },
|
||||||
|
select: { id: true }
|
||||||
|
});
|
||||||
|
return (result?.id ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSSHKeyFile(serverId, sshKey) {
|
||||||
|
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
||||||
|
const keyPath = join(sshKeysDir, `server_${serverId}_key`);
|
||||||
|
|
||||||
|
// Normalize the key: trim any trailing whitespace and ensure exactly one newline at the end
|
||||||
|
const normalizedKey = sshKey.trimEnd() + '\n';
|
||||||
|
writeFileSync(keyPath, normalizedKey);
|
||||||
|
chmodSync(keyPath, 0o600); // Set proper permissions
|
||||||
|
|
||||||
|
return keyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
let dbInstance = null;
|
||||||
|
|
||||||
|
export function getDatabase() {
|
||||||
|
dbInstance ??= new DatabaseServicePrisma();
|
||||||
|
return dbInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DatabaseServicePrisma;
|
||||||
279
src/server/database-prisma.ts
Normal file
279
src/server/database-prisma.ts
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import { prisma } from './db';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { writeFileSync, unlinkSync, chmodSync, mkdirSync } from 'fs';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import type { CreateServerData } from '../types/server';
|
||||||
|
|
||||||
|
class DatabaseServicePrisma {
|
||||||
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Ensure data/ssh-keys directory exists
|
||||||
|
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
||||||
|
if (!existsSync(sshKeysDir)) {
|
||||||
|
mkdirSync(sshKeysDir, { mode: 0o700 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server CRUD operations
|
||||||
|
async createServer(serverData: CreateServerData) {
|
||||||
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
|
||||||
|
let ssh_key_path = null;
|
||||||
|
|
||||||
|
// If using SSH key authentication, create persistent key file
|
||||||
|
if (auth_type === 'key' && ssh_key) {
|
||||||
|
const serverId = await this.getNextServerId();
|
||||||
|
ssh_key_path = this.createSSHKeyFile(serverId, ssh_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
ip,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
auth_type: auth_type ?? 'password',
|
||||||
|
ssh_key,
|
||||||
|
ssh_key_passphrase,
|
||||||
|
ssh_port: ssh_port ?? 22,
|
||||||
|
ssh_key_path,
|
||||||
|
key_generated: Boolean(key_generated),
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllServers() {
|
||||||
|
return await prisma.server.findMany({
|
||||||
|
orderBy: { created_at: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServerById(id: number) {
|
||||||
|
return await prisma.server.findUnique({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateServer(id: number, serverData: CreateServerData) {
|
||||||
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
|
||||||
|
// Get existing server to check for key changes
|
||||||
|
const existingServer = await this.getServerById(id);
|
||||||
|
let ssh_key_path = existingServer?.ssh_key_path;
|
||||||
|
|
||||||
|
// Handle SSH key changes
|
||||||
|
if (auth_type === 'key' && ssh_key) {
|
||||||
|
// Delete old key file if it exists
|
||||||
|
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(existingServer.ssh_key_path);
|
||||||
|
// Also delete public key file if it exists
|
||||||
|
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete old SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new key file
|
||||||
|
ssh_key_path = this.createSSHKeyFile(id, ssh_key);
|
||||||
|
} else if (auth_type !== 'key') {
|
||||||
|
// If switching away from key auth, delete key files
|
||||||
|
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(existingServer.ssh_key_path);
|
||||||
|
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ssh_key_path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
ip,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
auth_type: auth_type ?? 'password',
|
||||||
|
ssh_key,
|
||||||
|
ssh_key_passphrase,
|
||||||
|
ssh_port: ssh_port ?? 22,
|
||||||
|
ssh_key_path,
|
||||||
|
key_generated: key_generated !== undefined ? Boolean(key_generated) : (existingServer?.key_generated ?? false),
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteServer(id: number) {
|
||||||
|
// Get server info before deletion to clean up key files
|
||||||
|
const server = await this.getServerById(id);
|
||||||
|
|
||||||
|
// Delete SSH key files if they exist
|
||||||
|
if (server?.ssh_key_path && existsSync(server.ssh_key_path)) {
|
||||||
|
try {
|
||||||
|
unlinkSync(server.ssh_key_path);
|
||||||
|
const pubKeyPath = server.ssh_key_path + '.pub';
|
||||||
|
if (existsSync(pubKeyPath)) {
|
||||||
|
unlinkSync(pubKeyPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to delete SSH key file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.server.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installed Scripts CRUD operations
|
||||||
|
async createInstalledScript(scriptData: {
|
||||||
|
script_name: string;
|
||||||
|
script_path: string;
|
||||||
|
container_id?: string;
|
||||||
|
server_id?: number;
|
||||||
|
execution_mode: string;
|
||||||
|
status: 'in_progress' | 'success' | 'failed';
|
||||||
|
output_log?: string;
|
||||||
|
web_ui_ip?: string;
|
||||||
|
web_ui_port?: number;
|
||||||
|
}) {
|
||||||
|
const { script_name, script_path, container_id, server_id, execution_mode, status, output_log, web_ui_ip, web_ui_port } = scriptData;
|
||||||
|
|
||||||
|
return await prisma.installedScript.create({
|
||||||
|
data: {
|
||||||
|
script_name,
|
||||||
|
script_path,
|
||||||
|
container_id: container_id ?? null,
|
||||||
|
server_id: server_id ?? null,
|
||||||
|
execution_mode,
|
||||||
|
status,
|
||||||
|
output_log: output_log ?? null,
|
||||||
|
web_ui_ip: web_ui_ip ?? null,
|
||||||
|
web_ui_port: web_ui_port ?? null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllInstalledScripts() {
|
||||||
|
return await prisma.installedScript.findMany({
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
},
|
||||||
|
orderBy: { installation_date: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstalledScriptById(id: number) {
|
||||||
|
return await prisma.installedScript.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstalledScriptsByServer(server_id: number) {
|
||||||
|
return await prisma.installedScript.findMany({
|
||||||
|
where: { server_id },
|
||||||
|
include: {
|
||||||
|
server: true
|
||||||
|
},
|
||||||
|
orderBy: { installation_date: 'desc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateInstalledScript(id: number, updateData: {
|
||||||
|
script_name?: string;
|
||||||
|
container_id?: string;
|
||||||
|
status?: 'in_progress' | 'success' | 'failed';
|
||||||
|
output_log?: string;
|
||||||
|
web_ui_ip?: string;
|
||||||
|
web_ui_port?: number;
|
||||||
|
}) {
|
||||||
|
const { script_name, container_id, status, output_log, web_ui_ip, web_ui_port } = updateData;
|
||||||
|
|
||||||
|
const updateFields: {
|
||||||
|
script_name?: string;
|
||||||
|
container_id?: string;
|
||||||
|
status?: 'in_progress' | 'success' | 'failed';
|
||||||
|
output_log?: string;
|
||||||
|
web_ui_ip?: string;
|
||||||
|
web_ui_port?: number;
|
||||||
|
} = {};
|
||||||
|
if (script_name !== undefined) updateFields.script_name = script_name;
|
||||||
|
if (container_id !== undefined) updateFields.container_id = container_id;
|
||||||
|
if (status !== undefined) updateFields.status = status;
|
||||||
|
if (output_log !== undefined) updateFields.output_log = output_log;
|
||||||
|
if (web_ui_ip !== undefined) updateFields.web_ui_ip = web_ui_ip;
|
||||||
|
if (web_ui_port !== undefined) updateFields.web_ui_port = web_ui_port;
|
||||||
|
|
||||||
|
if (Object.keys(updateFields).length === 0) {
|
||||||
|
return { changes: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.installedScript.update({
|
||||||
|
where: { id },
|
||||||
|
data: updateFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInstalledScript(id: number) {
|
||||||
|
return await prisma.installedScript.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInstalledScriptsByServer(server_id: number) {
|
||||||
|
return await prisma.installedScript.deleteMany({
|
||||||
|
where: { server_id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNextServerId() {
|
||||||
|
const result = await prisma.server.findFirst({
|
||||||
|
orderBy: { id: 'desc' },
|
||||||
|
select: { id: true }
|
||||||
|
});
|
||||||
|
return (result?.id ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSSHKeyFile(serverId: number, sshKey: string) {
|
||||||
|
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
||||||
|
const keyPath = join(sshKeysDir, `server_${serverId}_key`);
|
||||||
|
|
||||||
|
// Normalize the key: trim any trailing whitespace and ensure exactly one newline at the end
|
||||||
|
const normalizedKey = sshKey.trimEnd() + '\n';
|
||||||
|
writeFileSync(keyPath, normalizedKey);
|
||||||
|
chmodSync(keyPath, 0o600); // Set proper permissions
|
||||||
|
|
||||||
|
return keyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
let dbInstance: DatabaseServicePrisma | null = null;
|
||||||
|
|
||||||
|
export function getDatabase() {
|
||||||
|
dbInstance ??= new DatabaseServicePrisma();
|
||||||
|
return dbInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DatabaseServicePrisma;
|
||||||
@@ -1,476 +0,0 @@
|
|||||||
import Database from 'better-sqlite3';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { writeFileSync, unlinkSync, chmodSync, mkdirSync } from 'fs';
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
|
|
||||||
class DatabaseService {
|
|
||||||
constructor() {
|
|
||||||
const dbPath = join(process.cwd(), 'data', 'settings.db');
|
|
||||||
this.db = new Database(dbPath);
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Ensure data/ssh-keys directory exists
|
|
||||||
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
|
||||||
if (!existsSync(sshKeysDir)) {
|
|
||||||
mkdirSync(sshKeysDir, { mode: 0o700 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create servers table if it doesn't exist
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS servers (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
ip TEXT NOT NULL,
|
|
||||||
user TEXT NOT NULL,
|
|
||||||
password TEXT,
|
|
||||||
auth_type TEXT DEFAULT 'password' CHECK(auth_type IN ('password', 'key')),
|
|
||||||
ssh_key TEXT,
|
|
||||||
ssh_key_passphrase TEXT,
|
|
||||||
ssh_port INTEGER DEFAULT 22,
|
|
||||||
ssh_key_path TEXT,
|
|
||||||
key_generated INTEGER DEFAULT 0,
|
|
||||||
color TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Migration: Add new columns to existing servers table
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN auth_type TEXT DEFAULT 'password' CHECK(auth_type IN ('password', 'key'))
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN ssh_key TEXT
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN ssh_key_passphrase TEXT
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN ssh_port INTEGER DEFAULT 22
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN color TEXT
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN ssh_key_path TEXT
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE servers ADD COLUMN key_generated INTEGER DEFAULT 0
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing servers to have auth_type='password' if not set
|
|
||||||
this.db.exec(`
|
|
||||||
UPDATE servers SET auth_type = 'password' WHERE auth_type IS NULL
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Update existing servers to have ssh_port=22 if not set
|
|
||||||
this.db.exec(`
|
|
||||||
UPDATE servers SET ssh_port = 22 WHERE ssh_port IS NULL
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Migration: Convert 'both' auth_type to 'key'
|
|
||||||
this.db.exec(`
|
|
||||||
UPDATE servers SET auth_type = 'key' WHERE auth_type = 'both'
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Update existing servers to have key_generated=0 if not set
|
|
||||||
this.db.exec(`
|
|
||||||
UPDATE servers SET key_generated = 0 WHERE key_generated IS NULL
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Migration: Add web_ui_ip column to existing installed_scripts table
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE installed_scripts ADD COLUMN web_ui_ip TEXT
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migration: Add web_ui_port column to existing installed_scripts table
|
|
||||||
try {
|
|
||||||
this.db.exec(`
|
|
||||||
ALTER TABLE installed_scripts ADD COLUMN web_ui_port INTEGER
|
|
||||||
`);
|
|
||||||
} catch (e) {
|
|
||||||
// Column already exists, ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create installed_scripts table if it doesn't exist
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS installed_scripts (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
script_name TEXT NOT NULL,
|
|
||||||
script_path TEXT NOT NULL,
|
|
||||||
container_id TEXT,
|
|
||||||
server_id INTEGER,
|
|
||||||
execution_mode TEXT NOT NULL CHECK(execution_mode IN ('local', 'ssh')),
|
|
||||||
installation_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
status TEXT NOT NULL CHECK(status IN ('in_progress', 'success', 'failed')),
|
|
||||||
output_log TEXT,
|
|
||||||
web_ui_ip TEXT,
|
|
||||||
web_ui_port INTEGER,
|
|
||||||
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE SET NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create trigger to update updated_at on row update
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_servers_timestamp
|
|
||||||
AFTER UPDATE ON servers
|
|
||||||
BEGIN
|
|
||||||
UPDATE servers SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server CRUD operations
|
|
||||||
/**
|
|
||||||
* @param {import('../types/server').CreateServerData} serverData
|
|
||||||
*/
|
|
||||||
createServer(serverData) {
|
|
||||||
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
|
||||||
|
|
||||||
let ssh_key_path = null;
|
|
||||||
|
|
||||||
// If using SSH key authentication, create persistent key file
|
|
||||||
if (auth_type === 'key' && ssh_key) {
|
|
||||||
const serverId = this.getNextServerId();
|
|
||||||
ssh_key_path = this.createSSHKeyFile(serverId, ssh_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
INSERT INTO servers (name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, ssh_key_path, key_generated, color)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
return stmt.run(name, ip, user, password, auth_type || 'password', ssh_key, ssh_key_passphrase, ssh_port || 22, ssh_key_path, key_generated || 0, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllServers() {
|
|
||||||
const stmt = this.db.prepare('SELECT * FROM servers ORDER BY created_at DESC');
|
|
||||||
return stmt.all();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
*/
|
|
||||||
getServerById(id) {
|
|
||||||
const stmt = this.db.prepare('SELECT * FROM servers WHERE id = ?');
|
|
||||||
return stmt.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @param {import('../types/server').CreateServerData} serverData
|
|
||||||
*/
|
|
||||||
updateServer(id, serverData) {
|
|
||||||
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
|
||||||
|
|
||||||
// Get existing server to check for key changes
|
|
||||||
const existingServer = this.getServerById(id);
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
let ssh_key_path = existingServer?.ssh_key_path;
|
|
||||||
|
|
||||||
// Handle SSH key changes
|
|
||||||
if (auth_type === 'key' && ssh_key) {
|
|
||||||
// Delete old key file if it exists
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
|
||||||
try {
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
unlinkSync(existingServer.ssh_key_path);
|
|
||||||
// Also delete public key file if it exists
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
|
||||||
if (existsSync(pubKeyPath)) {
|
|
||||||
unlinkSync(pubKeyPath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to delete old SSH key file:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new key file
|
|
||||||
ssh_key_path = this.createSSHKeyFile(id, ssh_key);
|
|
||||||
} else if (auth_type !== 'key') {
|
|
||||||
// If switching away from key auth, delete key files
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
if (existingServer?.ssh_key_path && existsSync(existingServer.ssh_key_path)) {
|
|
||||||
try {
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
unlinkSync(existingServer.ssh_key_path);
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
const pubKeyPath = existingServer.ssh_key_path + '.pub';
|
|
||||||
if (existsSync(pubKeyPath)) {
|
|
||||||
unlinkSync(pubKeyPath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to delete SSH key file:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ssh_key_path = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
UPDATE servers
|
|
||||||
SET name = ?, ip = ?, user = ?, password = ?, auth_type = ?, ssh_key = ?, ssh_key_passphrase = ?, ssh_port = ?, ssh_key_path = ?, key_generated = ?, color = ?
|
|
||||||
WHERE id = ?
|
|
||||||
`);
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
return stmt.run(name, ip, user, password, auth_type || 'password', ssh_key, ssh_key_passphrase, ssh_port || 22, ssh_key_path, key_generated !== undefined ? key_generated : (existingServer?.key_generated || 0), color, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
*/
|
|
||||||
deleteServer(id) {
|
|
||||||
// Get server info before deletion to clean up key files
|
|
||||||
const server = this.getServerById(id);
|
|
||||||
|
|
||||||
// Delete SSH key files if they exist
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
if (server?.ssh_key_path && existsSync(server.ssh_key_path)) {
|
|
||||||
try {
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
unlinkSync(server.ssh_key_path);
|
|
||||||
// @ts-ignore - Database migration adds this column
|
|
||||||
const pubKeyPath = server.ssh_key_path + '.pub';
|
|
||||||
if (existsSync(pubKeyPath)) {
|
|
||||||
unlinkSync(pubKeyPath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to delete SSH key file:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stmt = this.db.prepare('DELETE FROM servers WHERE id = ?');
|
|
||||||
return stmt.run(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Installed Scripts CRUD operations
|
|
||||||
/**
|
|
||||||
* @param {Object} scriptData
|
|
||||||
* @param {string} scriptData.script_name
|
|
||||||
* @param {string} scriptData.script_path
|
|
||||||
* @param {string} [scriptData.container_id]
|
|
||||||
* @param {number} [scriptData.server_id]
|
|
||||||
* @param {string} scriptData.execution_mode
|
|
||||||
* @param {string} scriptData.status
|
|
||||||
* @param {string} [scriptData.output_log]
|
|
||||||
* @param {string} [scriptData.web_ui_ip]
|
|
||||||
* @param {number} [scriptData.web_ui_port]
|
|
||||||
*/
|
|
||||||
createInstalledScript(scriptData) {
|
|
||||||
const { script_name, script_path, container_id, server_id, execution_mode, status, output_log, web_ui_ip, web_ui_port } = scriptData;
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
INSERT INTO installed_scripts (script_name, script_path, container_id, server_id, execution_mode, status, output_log, web_ui_ip, web_ui_port)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
return stmt.run(script_name, script_path, container_id || null, server_id || null, execution_mode, status, output_log || null, web_ui_ip || null, web_ui_port || null);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllInstalledScripts() {
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
SELECT
|
|
||||||
inst.*,
|
|
||||||
s.name as server_name,
|
|
||||||
s.ip as server_ip,
|
|
||||||
s.user as server_user,
|
|
||||||
s.password as server_password,
|
|
||||||
s.auth_type as server_auth_type,
|
|
||||||
s.ssh_key as server_ssh_key,
|
|
||||||
s.ssh_key_passphrase as server_ssh_key_passphrase,
|
|
||||||
s.ssh_port as server_ssh_port,
|
|
||||||
s.color as server_color
|
|
||||||
FROM installed_scripts inst
|
|
||||||
LEFT JOIN servers s ON inst.server_id = s.id
|
|
||||||
ORDER BY inst.installation_date DESC
|
|
||||||
`);
|
|
||||||
return stmt.all();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
*/
|
|
||||||
getInstalledScriptById(id) {
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
SELECT
|
|
||||||
inst.*,
|
|
||||||
s.name as server_name,
|
|
||||||
s.ip as server_ip
|
|
||||||
FROM installed_scripts inst
|
|
||||||
LEFT JOIN servers s ON inst.server_id = s.id
|
|
||||||
WHERE inst.id = ?
|
|
||||||
`);
|
|
||||||
return stmt.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} server_id
|
|
||||||
*/
|
|
||||||
getInstalledScriptsByServer(server_id) {
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
SELECT
|
|
||||||
inst.*,
|
|
||||||
s.name as server_name,
|
|
||||||
s.ip as server_ip
|
|
||||||
FROM installed_scripts inst
|
|
||||||
LEFT JOIN servers s ON inst.server_id = s.id
|
|
||||||
WHERE inst.server_id = ?
|
|
||||||
ORDER BY inst.installation_date DESC
|
|
||||||
`);
|
|
||||||
return stmt.all(server_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @param {Object} updateData
|
|
||||||
* @param {string} [updateData.script_name]
|
|
||||||
* @param {string} [updateData.container_id]
|
|
||||||
* @param {string} [updateData.status]
|
|
||||||
* @param {string} [updateData.output_log]
|
|
||||||
* @param {string} [updateData.web_ui_ip]
|
|
||||||
* @param {number} [updateData.web_ui_port]
|
|
||||||
*/
|
|
||||||
updateInstalledScript(id, updateData) {
|
|
||||||
const { script_name, container_id, status, output_log, web_ui_ip, web_ui_port } = updateData;
|
|
||||||
const updates = [];
|
|
||||||
const values = [];
|
|
||||||
|
|
||||||
if (script_name !== undefined) {
|
|
||||||
updates.push('script_name = ?');
|
|
||||||
values.push(script_name);
|
|
||||||
}
|
|
||||||
if (container_id !== undefined) {
|
|
||||||
updates.push('container_id = ?');
|
|
||||||
values.push(container_id);
|
|
||||||
}
|
|
||||||
if (status !== undefined) {
|
|
||||||
updates.push('status = ?');
|
|
||||||
values.push(status);
|
|
||||||
}
|
|
||||||
if (output_log !== undefined) {
|
|
||||||
updates.push('output_log = ?');
|
|
||||||
values.push(output_log);
|
|
||||||
}
|
|
||||||
if (web_ui_ip !== undefined) {
|
|
||||||
updates.push('web_ui_ip = ?');
|
|
||||||
values.push(web_ui_ip);
|
|
||||||
}
|
|
||||||
if (web_ui_port !== undefined) {
|
|
||||||
updates.push('web_ui_port = ?');
|
|
||||||
values.push(web_ui_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updates.length === 0) {
|
|
||||||
return { changes: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
const stmt = this.db.prepare(`
|
|
||||||
UPDATE installed_scripts
|
|
||||||
SET ${updates.join(', ')}
|
|
||||||
WHERE id = ?
|
|
||||||
`);
|
|
||||||
return stmt.run(...values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
*/
|
|
||||||
deleteInstalledScript(id) {
|
|
||||||
const stmt = this.db.prepare('DELETE FROM installed_scripts WHERE id = ?');
|
|
||||||
return stmt.run(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} server_id
|
|
||||||
*/
|
|
||||||
deleteInstalledScriptsByServer(server_id) {
|
|
||||||
const stmt = this.db.prepare('DELETE FROM installed_scripts WHERE server_id = ?');
|
|
||||||
return stmt.run(server_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next available server ID for key file naming
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
getNextServerId() {
|
|
||||||
const stmt = this.db.prepare('SELECT MAX(id) as maxId FROM servers');
|
|
||||||
const result = stmt.get();
|
|
||||||
// @ts-ignore - SQL query result type
|
|
||||||
return (result?.maxId || 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create SSH key file and return the path
|
|
||||||
* @param {number} serverId
|
|
||||||
* @param {string} sshKey
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
createSSHKeyFile(serverId, sshKey) {
|
|
||||||
const sshKeysDir = join(process.cwd(), 'data', 'ssh-keys');
|
|
||||||
const keyPath = join(sshKeysDir, `server_${serverId}_key`);
|
|
||||||
|
|
||||||
// Normalize the key: trim any trailing whitespace and ensure exactly one newline at the end
|
|
||||||
const normalizedKey = sshKey.trimEnd() + '\n';
|
|
||||||
writeFileSync(keyPath, normalizedKey);
|
|
||||||
chmodSync(keyPath, 0o600); // Set proper permissions
|
|
||||||
|
|
||||||
return keyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.db.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton instance
|
|
||||||
/** @type {DatabaseService | null} */
|
|
||||||
let dbInstance = null;
|
|
||||||
|
|
||||||
export function getDatabase() {
|
|
||||||
if (!dbInstance) {
|
|
||||||
dbInstance = new DatabaseService();
|
|
||||||
}
|
|
||||||
return dbInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DatabaseService;
|
|
||||||
|
|
||||||
7
src/server/db.js
Normal file
7
src/server/db.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis;
|
||||||
|
|
||||||
|
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
||||||
9
src/server/db.ts
Normal file
9
src/server/db.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
||||||
@@ -8,11 +8,11 @@ export interface Server {
|
|||||||
ssh_key?: string;
|
ssh_key?: string;
|
||||||
ssh_key_passphrase?: string;
|
ssh_key_passphrase?: string;
|
||||||
ssh_key_path?: string;
|
ssh_key_path?: string;
|
||||||
key_generated?: number;
|
key_generated?: boolean;
|
||||||
ssh_port?: number;
|
ssh_port?: number;
|
||||||
color?: string;
|
color?: string;
|
||||||
created_at: string;
|
created_at: Date | null;
|
||||||
updated_at: string;
|
updated_at: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateServerData {
|
export interface CreateServerData {
|
||||||
@@ -24,7 +24,7 @@ export interface CreateServerData {
|
|||||||
ssh_key?: string;
|
ssh_key?: string;
|
||||||
ssh_key_passphrase?: string;
|
ssh_key_passphrase?: string;
|
||||||
ssh_key_path?: string;
|
ssh_key_path?: string;
|
||||||
key_generated?: number;
|
key_generated?: boolean;
|
||||||
ssh_port?: number;
|
ssh_port?: number;
|
||||||
color?: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
59
update.sh
59
update.sh
@@ -412,6 +412,36 @@ restore_backup_files() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure DATABASE_URL is set in .env file for Prisma
|
||||||
|
ensure_database_url() {
|
||||||
|
log "Ensuring DATABASE_URL is set in .env file..."
|
||||||
|
|
||||||
|
# Check if .env file exists
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
log_warning ".env file not found, creating from .env.example..."
|
||||||
|
if [ -f ".env.example" ]; then
|
||||||
|
cp ".env.example" ".env"
|
||||||
|
else
|
||||||
|
log_error ".env.example not found, cannot create .env file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if DATABASE_URL is already set
|
||||||
|
if grep -q "^DATABASE_URL=" .env; then
|
||||||
|
log "DATABASE_URL already exists in .env file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add DATABASE_URL to .env file
|
||||||
|
log "Adding DATABASE_URL to .env file..."
|
||||||
|
echo "" >> .env
|
||||||
|
echo "# Database" >> .env
|
||||||
|
echo "DATABASE_URL=\"file:./data/database.sqlite\"" >> .env
|
||||||
|
|
||||||
|
log_success "DATABASE_URL added to .env file"
|
||||||
|
}
|
||||||
|
|
||||||
# Check if systemd service exists
|
# Check if systemd service exists
|
||||||
check_service() {
|
check_service() {
|
||||||
# systemctl status returns 0-3 if service exists (running, exited, failed, etc.)
|
# systemctl status returns 0-3 if service exists (running, exited, failed, etc.)
|
||||||
@@ -607,6 +637,32 @@ install_and_build() {
|
|||||||
log_success "Dependencies installed successfully"
|
log_success "Dependencies installed successfully"
|
||||||
rm -f "$npm_log"
|
rm -f "$npm_log"
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
log "Generating Prisma client..."
|
||||||
|
if ! npx prisma generate > "$npm_log" 2>&1; then
|
||||||
|
log_error "Failed to generate Prisma client"
|
||||||
|
log_error "Prisma generate output:"
|
||||||
|
cat "$npm_log" | while read -r line; do
|
||||||
|
log_error "PRISMA: $line"
|
||||||
|
done
|
||||||
|
rm -f "$npm_log"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_success "Prisma client generated successfully"
|
||||||
|
|
||||||
|
# Run Prisma migrations
|
||||||
|
log "Running Prisma migrations..."
|
||||||
|
if ! npx prisma migrate deploy > "$npm_log" 2>&1; then
|
||||||
|
log_warning "Prisma migrations failed or no migrations to run"
|
||||||
|
log "Prisma migrate output:"
|
||||||
|
cat "$npm_log" | while read -r line; do
|
||||||
|
log "PRISMA: $line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_success "Prisma migrations completed successfully"
|
||||||
|
fi
|
||||||
|
rm -f "$npm_log"
|
||||||
|
|
||||||
log "Building application..."
|
log "Building application..."
|
||||||
# Set NODE_ENV to production for build
|
# Set NODE_ENV to production for build
|
||||||
export NODE_ENV=production
|
export NODE_ENV=production
|
||||||
@@ -838,6 +894,9 @@ main() {
|
|||||||
# Restore .env and data directory before building
|
# Restore .env and data directory before building
|
||||||
restore_backup_files
|
restore_backup_files
|
||||||
|
|
||||||
|
# Ensure DATABASE_URL is set for Prisma
|
||||||
|
ensure_database_url
|
||||||
|
|
||||||
# Install dependencies and build
|
# Install dependencies and build
|
||||||
if ! install_and_build; then
|
if ! install_and_build; then
|
||||||
log_error "Install and build failed, rolling back..."
|
log_error "Install and build failed, rolling back..."
|
||||||
|
|||||||
Reference in New Issue
Block a user