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
110 changes: 75 additions & 35 deletions ServerDeck/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Activity,
ArrowLeft,
Bot,
Home,
ChevronRight,
Copy,
Download,
Expand Down Expand Up @@ -108,6 +109,7 @@ import {
type TerminalThemePreset
} from "./data/terminalThemes";

const HOME_TAB_ID = "home";
const HOSTS_TAB_ID = "hosts";
const SFTP_TAB_ID = "sftp";
const AGENT_TAB_ID = "agent";
Expand Down Expand Up @@ -772,7 +774,7 @@ export default function App() {
const detachedSettingsWindow = isSettingsWindowView();
const preferenceSyncSource = detachedSettingsWindow ? "settings" : "main";

const [activeTabId, setActiveTabId] = useState(() => (detachedSettingsWindow ? SETTINGS_TAB_ID : HOSTS_TAB_ID));
const [activeTabId, setActiveTabId] = useState(() => (detachedSettingsWindow ? SETTINGS_TAB_ID : HOME_TAB_ID));
const [terminalTabs, setTerminalTabs] = useState<TerminalTab[]>([]);
const [monitorTabs, setMonitorTabs] = useState<MonitorTab[]>([]);
const [hosts, setHosts] = useState<SavedHost[]>([]);
Expand Down Expand Up @@ -853,12 +855,13 @@ export default function App() {
const fitAddonRef = useRef<FitAddon | null>(null);
const terminalDecodersRef = useRef<Map<string, TextDecoder>>(new Map());
const terminalBuffersRef = useRef<Map<string, string[]>>(new Map());
const activeTabIdRef = useRef(HOSTS_TAB_ID);
const previousTabIdRef = useRef(HOSTS_TAB_ID);
const activeTabIdRef = useRef(HOME_TAB_ID);
const previousTabIdRef = useRef(HOME_TAB_ID);
const terminalTabsRef = useRef<TerminalTab[]>([]);
const monitorTabsRef = useRef<MonitorTab[]>([]);
const skipPreferenceSyncRef = useRef(false);

const isHomeView = activeTabId === HOME_TAB_ID;
const isHostsView = activeTabId === HOSTS_TAB_ID;
const isSftpView = activeTabId === SFTP_TAB_ID;
const isAgentView = activeTabId === AGENT_TAB_ID;
Expand Down Expand Up @@ -2454,7 +2457,7 @@ export default function App() {
}

function closeSettings() {
const fallbackTabId = previousTabIdRef.current === SETTINGS_TAB_ID ? HOSTS_TAB_ID : previousTabIdRef.current;
const fallbackTabId = previousTabIdRef.current === SETTINGS_TAB_ID ? HOME_TAB_ID : previousTabIdRef.current;
setActiveTabId(fallbackTabId);
}

Expand Down Expand Up @@ -3286,7 +3289,7 @@ export default function App() {
const nextTabs = currentTabs.filter((tab) => tab.id !== tabId);
const nextActiveTabId =
activeTabIdRef.current === tabId
? nextTabs[targetIndex]?.id ?? nextTabs[targetIndex - 1]?.id ?? HOSTS_TAB_ID
? nextTabs[targetIndex]?.id ?? nextTabs[targetIndex - 1]?.id ?? HOME_TAB_ID
: activeTabIdRef.current;

setTerminalTabs(nextTabs);
Expand Down Expand Up @@ -3390,7 +3393,7 @@ export default function App() {

const targetTab = currentTabs[targetIndex];
const nextTabs = currentTabs.filter((tab) => tab.id !== tabId);
const nextActiveTabId = activeTabIdRef.current === tabId ? HOSTS_TAB_ID : activeTabIdRef.current;
const nextActiveTabId = activeTabIdRef.current === tabId ? HOME_TAB_ID : activeTabIdRef.current;

setMonitorTabs(nextTabs);
setActiveTabId(nextActiveTabId);
Expand Down Expand Up @@ -3484,33 +3487,6 @@ export default function App() {
{sidebarCollapsed ? <PanelLeftOpen size={16} /> : <PanelLeftClose size={16} />}
</button>

<button
type="button"
className={`top-tab ${isHostsView ? "top-tab--active" : ""}`}
onClick={() => setActiveTabId(HOSTS_TAB_ID)}
>
<Server size={16} />
{messages.hosts}
</button>

<button
type="button"
className={`top-tab ${isSftpView ? "top-tab--active" : ""}`}
onClick={() => setActiveTabId(SFTP_TAB_ID)}
>
<FolderOpen size={16} />
{messages.sftp}
</button>

<button
type="button"
className={`top-tab ${isAgentView ? "top-tab--active" : ""}`}
onClick={() => setActiveTabId(AGENT_TAB_ID)}
>
<Bot size={16} />
{messages.agent}
</button>

{terminalTabs.map((tab) => (
<div
key={tab.id}
Expand Down Expand Up @@ -3645,13 +3621,31 @@ export default function App() {
<div className={`workspace ${sidebarCollapsed ? "workspace--sidebar-collapsed" : ""}`}>
<aside className={`sidebar ${sidebarCollapsed ? "sidebar--collapsed" : ""}`}>
<div className="sidebar__top">
<button
type="button"
className={`side-nav ${isHomeView ? "side-nav--active" : ""}`}
onClick={() => setActiveTabId(HOME_TAB_ID)}
>
<Home size={18} />
<span>{messages.home}</span>
</button>

<button
type="button"
className={`side-nav ${isHostsView ? "side-nav--active" : ""}`}
onClick={() => setActiveTabId(HOSTS_TAB_ID)}
>
<Server size={18} />
<span>Hosts</span>
<span>{messages.hosts}</span>
</button>

<button
type="button"
className={`side-nav ${isSftpView ? "side-nav--active" : ""}`}
onClick={() => setActiveTabId(SFTP_TAB_ID)}
>
<FolderOpen size={18} />
<span>{messages.sftp}</span>
</button>

<button
Expand Down Expand Up @@ -3688,7 +3682,53 @@ export default function App() {
</aside>

<main className="mainpane">
{isHostsView ? (
{isHomeView ? (
<section className="home-screen">
<div className="home-screen__inner">
<div className="home-screen__badge">SD</div>
<h1>ServerDeck</h1>
<p>{messages.homeDescription}</p>

<div className="home-screen__actions">
<button type="button" className="primary-button" onClick={() => setActiveTabId(HOSTS_TAB_ID)}>
<Server size={16} />
{messages.hosts}
</button>
<button type="button" className="secondary-button" onClick={() => setActiveTabId(SFTP_TAB_ID)}>
<FolderOpen size={16} />
{messages.sftp}
</button>
<button type="button" className="secondary-button" onClick={() => setActiveTabId(AGENT_TAB_ID)}>
<Bot size={16} />
{messages.agent}
</button>
<button type="button" className="secondary-button" onClick={() => void handleOpenLocalTerminal({ source: "default" })}>
<Plus size={16} />
{messages.localTerminal}
</button>
</div>

<div className="home-screen__stats">
<div className="home-stat-card">
<strong>{projects.length}</strong>
<span>{messages.projects}</span>
</div>
<div className="home-stat-card">
<strong>{hosts.length}</strong>
<span>{messages.hosts}</span>
</div>
<div className="home-stat-card">
<strong>{terminalTabs.length}</strong>
<span>{messages.terminal}</span>
</div>
<div className="home-stat-card">
<strong>{agentSessions.length}</strong>
<span>{messages.agentSessions}</span>
</div>
</div>
</div>
</section>
) : isHostsView ? (
<section className="hosts-screen">
<div className="hosts-toolbar">
<div className="searchbar">
Expand Down
8 changes: 8 additions & 0 deletions ServerDeck/src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ type Messages = {
monitorInfoCopied: string;
copied: string;
copyServerInfo: string;
home: string;
homeDescription: string;
hosts: string;
sftp: string;
newTab: string;
Expand Down Expand Up @@ -425,6 +427,8 @@ export const messagesByLanguage: Record<AppLanguage, Messages> = {
monitorInfoCopied: "Server info copied",
copied: "Copied",
copyServerInfo: "Copy Server Info",
home: "Home",
homeDescription: "Start from one clear place, then jump into hosts, SFTP, agent, or a local terminal.",
hosts: "Hosts",
sftp: "SFTP",
newTab: "New Tab",
Expand Down Expand Up @@ -748,6 +752,8 @@ export const messagesByLanguage: Record<AppLanguage, Messages> = {
monitorInfoCopied: "已复制服务器信息",
copied: "已复制",
copyServerInfo: "一键复制配置信息",
home: "首页",
homeDescription: "先从一个清晰入口开始,再进入主机、SFTP、Agent 或本地终端。",
hosts: "主机",
sftp: "SFTP",
newTab: "新标签页",
Expand Down Expand Up @@ -1071,6 +1077,8 @@ export const messagesByLanguage: Record<AppLanguage, Messages> = {
monitorInfoCopied: "サーバー情報をコピーしました",
copied: "コピー済み",
copyServerInfo: "設定情報をコピー",
home: "ホーム",
homeDescription: "まずは分かりやすい入口から始めて、そこから Hosts、SFTP、Agent、ローカルターミナルへ進みます。",
hosts: "ホスト",
sftp: "SFTP",
newTab: "新しいタブ",
Expand Down
85 changes: 85 additions & 0 deletions ServerDeck/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ select {
overflow: hidden;
}

.home-screen,
.hosts-screen,
.sftp-screen,
.settings-screen,
Expand All @@ -558,12 +559,88 @@ select {
overflow: hidden;
}

.home-screen,
.hosts-screen,
.sftp-screen,
.settings-screen {
padding: 0;
}

.home-screen {
display: grid;
place-items: center;
padding: 32px;
}

.home-screen__inner {
width: min(720px, 100%);
display: grid;
justify-items: center;
gap: 18px;
text-align: center;
}

.home-screen__badge {
width: 72px;
height: 72px;
display: grid;
place-items: center;
border-radius: 20px;
background: var(--chrome-tab-active-bg);
border: 1px solid var(--chrome-tab-active-border);
color: var(--content-text);
font-size: 26px;
font-weight: 700;
}

.home-screen__inner h1 {
margin: 0;
font-size: 34px;
line-height: 1.1;
}

.home-screen__inner p {
margin: 0;
max-width: 560px;
color: var(--content-muted);
font-size: 14px;
line-height: 1.7;
}

.home-screen__actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
}

.home-screen__stats {
width: 100%;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-top: 8px;
}

.home-stat-card {
display: grid;
gap: 6px;
padding: 18px 14px;
border-radius: 18px;
border: 1px solid var(--content-border);
background: var(--content-raised-bg);
}

.home-stat-card strong {
font-size: 24px;
color: var(--content-text);
}

.home-stat-card span {
color: var(--content-muted);
font-size: 12px;
}

.sftp-screen {
display: grid;
grid-template-rows: auto 1fr auto;
Expand Down Expand Up @@ -4462,6 +4539,14 @@ select {
align-items: stretch;
}

.home-screen {
padding: 20px;
}

.home-screen__stats {
grid-template-columns: repeat(2, minmax(0, 1fr));
}

.topbar__brand {
min-width: auto;
}
Expand Down
Loading