Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rustmius"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
authors = ["Cleboost"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
26 changes: 26 additions & 0 deletions src/class/Class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useKeysStore } from "@/stores/keys";
import { KeyPair } from "@/types/key";
import { homeDir } from "@tauri-apps/api/path";

export default class Key {
readonly id: number;
readonly keysStore = useKeysStore();
readonly conf: KeyPair

constructor(id: number) {
this.id = id;
this.conf = this.keysStore.getKey(id);
}

getID(): number {
return this.id;
}

getName(): string {
return this.conf?.name || "N/A";
}

async getPath(): Promise<string> {
return `${await homeDir()}/.ssh/${this.conf?.name}`;
}
}
143 changes: 69 additions & 74 deletions src/class/Server.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,28 @@
import { useServersStore } from "@/stores/servers";
import { useConsolesStore } from "@/stores/consoles";
import { useServerConfigStore } from "@/stores/servers";
import { Server as ServerType } from "@/types/server";
import { Command } from "@tauri-apps/plugin-shell";
import Key from "./Class";

export default class Server {
id: string;
server?: ServerType;
private serversStore = useServersStore();
private consolesStore = useConsolesStore();
public readonly id: ServerType["id"];
configClass: ConfigServer | undefined;
console: ServerConsole | undefined;
readonly serversStore = useServerConfigStore();

constructor(id: string) {
this.id = id;
this.loadServer();
}

private async loadServer(): Promise<void> {
await this.serversStore.load();
this.server = this.serversStore.findServerById(this.id);
}

async ensureLoaded(): Promise<void> {
await this.loadServer();
}

getName(): string {
return this.server?.name || "Unknown";
}

getIP(): string {
return this.server?.ip || "Unknown";
}

getKeyID(): number {
return this.server?.keyID || 0;
}

async launchTerminal(): Promise<void> {
try {
await this.consolesStore.launchNativeTerminal(this.id);
} catch (error) {
console.error(`Failed to launch terminal for ${this.getName()}:`, error);
throw error;
}
}

async testConnection(): Promise<boolean> {
try {
console.log(
`Testing SSH connection to ${this.getName()} (${this.getIP()})`,
);
return true;
} catch (error) {
console.error(`SSH test failed for ${this.getName()}:`, error);
return false;
}
this.configClass = new ConfigServer(this.serversStore.getServer(id));
this.console = new ServerConsole(this);
}

config(): ConfigServer {
return new ConfigServer(this.server);
return this.configClass;
}
}

class ConfigServer {
server: ServerType | undefined;
private serversStore = useServersStore();
export class ConfigServer {
server: ServerType;
readonly serversStore = useServerConfigStore();

constructor(server: ServerType | undefined) {
this.server = server;
Expand All @@ -72,6 +32,10 @@ class ConfigServer {
return this.server;
}

getID(): string {
return this.server.id;
}

getName(): string {
return this.server?.name || "Unknown";
}
Expand All @@ -84,29 +48,13 @@ class ConfigServer {
return this.server?.keyID || 0;
}

getKey(): undefined {
return undefined;
}

setName(name: string): void {
if (this.server) {
this.server.name = name;
this.serversStore.updateServer(this.server.id, this.server);
}
}

setIP(ip: string): void {
if (this.server) {
this.server.ip = ip;
this.serversStore.updateServer(this.server.id, this.server);
}
getKey(): Key {
return new Key(this.getKeyID());
}

setKeyID(keyID: number): void {
if (this.server) {
this.server.keyID = keyID;
this.serversStore.updateServer(this.server.id, this.server);
}
update(server: ServerType): void {
this.server = { ...this.server, ...server };
this.serversStore.updateServer(this.server.id, this.server);
}

isValid(): boolean {
Expand All @@ -129,3 +77,50 @@ class ConfigServer {
return undefined;
}
}

export class ServerConsole {
readonly server: Server;

constructor(server: Server) {
this.server = server;
}

async create(): Promise<void> {
const sshArgs = [
"ssh",
"-tt",
"-o",
"StrictHostKeyChecking=accept-new",
"-i",
await this.server.config().getKey().getPath(),
`root@${this.server.config().getIP()}`,
];
const candidates: Array<{ bin: string; args: string[] }> = [
{ bin: "foot", args: ["-e", ...sshArgs] },
{ bin: "alacritty", args: ["-e", ...sshArgs] },
{ bin: "kitty", args: ["-e", ...sshArgs] },
{ bin: "wezterm", args: ["start", "--", ...sshArgs] },
{ bin: "gnome-terminal", args: ["--", ...sshArgs] },
{ bin: "xfce4-terminal", args: ["-e", sshArgs.join(" ")] },
{ bin: "konsole", args: ["-e", ...sshArgs] },
{ bin: "tilix", args: ["-e", ...sshArgs] },
{ bin: "lxterminal", args: ["-e", ...sshArgs] },
{ bin: "xterm", args: ["-e", ...sshArgs] },
{ bin: "footclient", args: ["-e", ...sshArgs] },
];

let lastErr: unknown;
for (const c of candidates) {
try {
console.log(`Trying terminal: ${c.bin} with args:`, c.args);
await Command.create(c.bin, c.args).spawn();
return;
} catch (err) {
lastErr = err;
console.log(`❌ Failed to open ${c.bin}:`, err);
}
}
console.error("❌ No terminal emulator available. Last error:", lastErr);
throw lastErr ?? new Error("No terminal emulator available");
}
}
27 changes: 14 additions & 13 deletions src/components/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { RouterLink, useRoute } from "vue-router";
import { useServerInstancesStore } from "@/stores/serverInstances";
import { computed } from "vue";
import { useServerInstanceStore } from "@/stores/serverInstance";

const route = useRoute();
const serverInstancesStore = useServerInstancesStore();
const instanceStore = useServerInstanceStore();

const serverInstance = computed(() => {
return instanceStore.listServerInstances();
});

function isRouteActive(routePath: string): boolean {
return routePath === route.path;
Expand All @@ -27,7 +32,6 @@ const routes = [
class="flex flex-col h-dvh pb-2 pt-2 px-2 justify-between bg-sidebar"
>
<div class="flex flex-col gap-1">
<!-- Fixed Routes (Servers, SSH Keys) -->
<nav class="grid gap-1">
<Tooltip v-for="route in routes" :key="route.name">
<TooltipTrigger as-child>
Expand All @@ -50,19 +54,14 @@ const routes = [
</TooltipContent>
</Tooltip>
</nav>

<!-- Separator -->
<div
v-if="serverInstancesStore.sidebarInstances.length > 0"
v-if="serverInstance.length > 0"
class="border-t border-sidebar-border my-2"
></div>

<nav
v-if="serverInstancesStore.sidebarInstances.length > 0"
class="grid gap-1"
>
<nav v-if="serverInstance.length > 0" class="grid gap-1">
<div
v-for="instance in serverInstancesStore.sidebarInstances"
v-for="instance in serverInstance"
:key="instance.id"
class="group relative"
>
Expand All @@ -78,14 +77,16 @@ const routes = [
: ''
"
class="rounded-lg"
:aria-label="instance.name"
:aria-label="
instance.config().getName() || 'N/A'
"
>
<Icon icon="lucide:server" class="size-5" />
</Button>
</RouterLink>
</TooltipTrigger>
<TooltipContent side="right" :side-offset="5">
{{ instance.name }}
{{ instance.config().getName() || "N/A" }}
</TooltipContent>
</Tooltip>
</div>
Expand Down
Loading