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
26 changes: 22 additions & 4 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,31 @@
},
"WorkspaceEmpty": {
"LoginHint": "Sign in to view assets and connect",
"ConnectAsset": "Right-click asset → Connect",
"ConnectAssetHint": " to choose a connection method",
"ExpandNode": "Right-click node → Expand all",
"ExpandNodeHint": " to expand all assets under the node",
"Shortcuts": "Session shortcuts",
"SwitchSession": "Switch session",
"Or": "or"
},
"WorkspaceTour": {
"ConnectAssetTitle": "Right-click asset → Connect",
"ConnectAssetDescription": "You can choose a connection method again",
"ExpandNodeTitle": "Right-click node → Expand all",
"ExpandNodeDescription": "Expand all assets under the node",
"AddSessionTitle": "Click + in the header to create a session",
"AddSessionDescription": "Search for an asset and open a new connection",
"progress": "Step {current} of {total}",
"next": "Next",
"previous": "Previous",
"done": "Done"
},
"AddSession": {
"NewConnection": "New connection",
"SearchPlaceholder": "Search by asset name or address…",
"Local": "Local",
"LocalShell": "Local Shell",
"LocalShellHint": "Open a local terminal",
"SearchResults": "Search results",
"NoMatchingAssets": "No matching assets"
},
"Auth": {
"SignInTitle": "Sign in from your browser to continue",
"BrowserHintPrefix": "If your browser hasn't opened for you to sign in,",
Expand Down
26 changes: 22 additions & 4 deletions i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,31 @@
},
"WorkspaceEmpty": {
"LoginHint": "登录后查看资产并发起连接",
"ConnectAsset": "右击资产 → 连接",
"ConnectAssetHint": ",可重新选择连接方式",
"ExpandNode": "右击节点 → 展开全部",
"ExpandNodeHint": ",展开节点下的所有资产",
"Shortcuts": "会话快捷键",
"SwitchSession": "切换会话",
"Or": "或"
},
"WorkspaceTour": {
"ConnectAssetTitle": "右击资产 → 连接",
"ConnectAssetDescription": "可重新选择连接方式",
"ExpandNodeTitle": "右击节点 → 展开全部",
"ExpandNodeDescription": "展开该节点下的所有资产",
"AddSessionTitle": "点击顶栏 + 新建会话",
"AddSessionDescription": "搜索资产并打开新的连接",
"progress": "第 {current} / {total} 步",
"next": "下一步",
"previous": "上一步",
"done": "完成"
},
"AddSession": {
"NewConnection": "新建连接",
"SearchPlaceholder": "搜索资产名称或地址…",
"Local": "本地",
"LocalShell": "Local Shell",
"LocalShellHint": "打开本机终端",
"SearchResults": "搜索结果",
"NoMatchingAssets": "未找到匹配资产"
},
"Auth": {
"SignInTitle": "请在浏览器中完成登录",
"BrowserHintPrefix": "如果浏览器没有自动为你打开登录页面",
Expand Down
2 changes: 1 addition & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default defineNuxtConfig({
mode: "out-in"
}
},
css: ["@/assets/css/main.css"],
css: ["@/assets/css/main.css", "@/assets/css/workspace-tour.css"],
icon: {
provider: "none",
fallbackToApi: false,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"@jumpserver/online-player": "workspace:*",
"@lezer/highlight": "^1.2.3",
"@nuxt/icon": "2.2.2",
"@nuxt/ui": "4.8.0",
"@nuxt/ui": "4.11.0",
"@nuxtjs/i18n": "10.4.0",
"@pinia/nuxt": "^0.11.3",
"@uiw/codemirror-theme-dracula": "^4.25.11",
Expand Down
690 changes: 453 additions & 237 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions ui/assets/css/workspace-tour.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
.driver-popover.workspace-driver-popover {
box-sizing: border-box;
width: min(320px, calc(100vw - 24px));
min-width: 260px;
max-width: 320px;
border: 1px solid var(--app-border-strong);
border-radius: 8px;
background: var(--app-surface-overlay);
color: var(--app-fg);
box-shadow: 0 14px 36px color-mix(in srgb, #000 34%, transparent);
font-family: inherit;
padding: 14px;
}

.driver-popover.workspace-driver-popover .driver-popover-title {
padding-right: 32px;
font-size: 14px;
font-weight: 600;
line-height: 1.4;
color: var(--app-fg);
}

.driver-popover.workspace-driver-popover .driver-popover-description {
margin-top: 6px;
color: var(--app-muted);
font-size: 12px;
line-height: 1.65;
}

.driver-popover.workspace-driver-popover .driver-popover-close-btn {
all: unset;
box-sizing: border-box;
position: absolute;
top: 6px;
right: 6px;
display: grid;
place-items: center;
width: 22px;
height: 22px;
cursor: pointer;
color: var(--app-muted);
font-size: 16px;
line-height: 1;
}

.driver-popover.workspace-driver-popover .driver-popover-close-btn:hover,
.driver-popover.workspace-driver-popover .driver-popover-close-btn:focus {
color: var(--app-fg);
}

.driver-popover.workspace-driver-popover .driver-popover-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 13px;
}

.driver-popover.workspace-driver-popover .driver-popover-progress-text {
color: var(--app-muted);
font-size: 11px;
}

.driver-popover.workspace-driver-popover .driver-popover-navigation-btns {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
flex-grow: 0;
gap: 4px;
}

.driver-popover.workspace-driver-popover .driver-popover-footer-btn {
all: unset;
appearance: none;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 27px;
padding: 4px 9px;
border: 1px solid var(--app-border-strong);
border-radius: 5px;
background: var(--app-panel-bg);
color: var(--app-fg);
font-size: 11.5px;
line-height: 1.2;
cursor: pointer;
}

.driver-popover.workspace-driver-popover .driver-popover-footer-btn:hover,
.driver-popover.workspace-driver-popover .driver-popover-footer-btn:focus {
background: var(--app-hover-strong);
}

.driver-popover.workspace-driver-popover .driver-popover-next-btn {
border-color: var(--theme-accent);
background: var(--theme-accent);
color: var(--app-accent-foreground);
}

.driver-popover.workspace-driver-popover .driver-popover-next-btn:hover,
.driver-popover.workspace-driver-popover .driver-popover-next-btn:focus {
background: color-mix(in srgb, var(--theme-accent) 86%, var(--app-fg));
}

.driver-popover.workspace-driver-popover .driver-popover-arrow {
border-color: var(--app-surface-overlay);
}
13 changes: 8 additions & 5 deletions ui/chen/components/WorkspaceModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ const props = defineProps<{
ui?: ModalProps["ui"];
}>();

const workspaceUi = computed<ModalProps["ui"]>(() => ({
...props.ui,
overlay: ["!absolute z-40 bg-elevated/75 backdrop-blur-none", props.ui?.overlay],
content: ["!absolute z-40 !w-[calc(100%-2rem)] !max-h-[calc(100%-2rem)]", props.ui?.content]
}));
const workspaceUi = computed(
() =>
({
...props.ui,
overlay: ["!absolute z-40 bg-elevated/75 backdrop-blur-none", props.ui?.overlay],
content: ["!absolute z-40 !w-[calc(100%-2rem)] !max-h-[calc(100%-2rem)]", props.ui?.content]
}) as ModalProps["ui"]
);
</script>

<template>
Expand Down
1 change: 1 addition & 0 deletions ui/components/SideBar/assetTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ defineExpose({ refresh, loading });
:class="open === false ? 'h-8 shrink-0' : 'min-h-0 flex-1'"
role="tree"
:aria-label="t('Menu.Resource')"
:data-workspace-tour="search.trim() ? undefined : 'assets'"
>
<div
v-if="!loggedIn"
Expand Down
6 changes: 6 additions & 0 deletions ui/components/SideBar/assetTreeNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const { t } = useI18n();
const isParent = computed(() => Boolean(props.node.isParent || props.node.children?.length));
const isOpen = computed(() => Boolean(props.node.open));
const isChecked = computed(() => props.checkedAssetIds?.includes(props.node.id) || false);
const workspaceTourTarget = computed(() => {
if (props.searchMode || props.batchMode) return undefined;
if (isParent.value && props.node.meta?.type !== "recent-connections") return "node";
return undefined;
});
const iconCandidates = computed(() => {
const iconSkin = (props.node.iconSkin || "").toLowerCase();
const data = props.node.meta?.data || {};
Expand Down Expand Up @@ -127,6 +132,7 @@ const activate = () => {
]"
:style="{ paddingLeft: `${10 + (node.level || 0) * 14}px` }"
:title="node.title || node.name"
:data-workspace-tour="workspaceTourTarget"
@click="activate"
@contextmenu.prevent="emit('contextmenu', node, $event)"
>
Expand Down
28 changes: 18 additions & 10 deletions ui/components/Workspace/addSessionPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ const { fetchTree, treeNodeToAsset } = useAssetTree();
const { recentConnections } = useRecentConnections();
const userInfoStore = useUserInfoStore();
const { openLocalShell } = useWorkspaceTabs();
const { t } = useI18n();
const localShellAvailable = computed(() => isDesktopRuntime());
const listTitle = computed(() => (search.value.trim() ? t("AddSession.SearchResults") : t("Menu.RecentConnections")));
const listEmpty = computed(() =>
search.value.trim() ? t("AddSession.NoMatchingAssets") : t("RecentConnections.Empty")
);

function flatten(nodes: AssetTreeNode[]): AssetItem[] {
return nodes.flatMap((node) => (node.isParent ? flatten(node.children || []) : [treeNodeToAsset(node)]));
Expand Down Expand Up @@ -52,32 +57,35 @@ watch(open, (value) => {

<template>
<UPopover v-model:open="open" :content="{ align: 'start', side: 'bottom', sideOffset: 8 }" :ui="{ content: 'p-0' }">
<UTooltip text="新建连接" :delay-duration="150">
<UTooltip :text="t('AddSession.NewConnection')" :delay-duration="150">
<button
type="button"
class="grid size-6 shrink-0 place-items-center rounded-lg text-muted transition-colors hover:bg-[var(--app-hover-strong)] hover:text-highlighted"
aria-label="新建连接"
data-workspace-tour="add-session"
class="grid size-6 shrink-0 place-items-center rounded-lg text-muted transition-colors hover:bg-(--app-hover-strong) hover:text-highlighted"
:aria-label="t('AddSession.NewConnection')"
>
<UIcon name="i-lucide-plus" class="size-4" />
</button>
</UTooltip>

<template #content>
<div class="w-[360px] overflow-hidden rounded-xl bg-default shadow-xl ring-1 ring-black/10 dark:ring-white/12">
<div class="w-90 overflow-hidden rounded-xl bg-default shadow-xl ring-1 ring-black/10 dark:ring-white/12">
<div class="border-b border-default p-2">
<UInput
v-model="search"
autofocus
icon="i-lucide-search"
size="sm"
variant="none"
placeholder="搜索资产名称或地址…"
:placeholder="t('AddSession.SearchPlaceholder')"
:ui="{ base: 'h-8 rounded-lg bg-elevated/70 ring-1 ring-inset ring-default' }"
/>
</div>
<div class="max-h-80 min-h-32 overflow-y-auto p-1.5">
<template v-if="localShellAvailable && !search.trim()">
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">本地</div>
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">
{{ t("AddSession.Local") }}
</div>
<button
type="button"
class="flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left hover:bg-elevated"
Expand All @@ -87,14 +95,14 @@ watch(open, (value) => {
<UIcon name="i-lucide-square-terminal" class="size-4 text-muted" />
</span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm text-highlighted">Local Shell</span>
<span class="block truncate text-[11px] text-muted">打开本机终端</span>
<span class="block truncate text-sm text-highlighted">{{ t("AddSession.LocalShell") }}</span>
<span class="block truncate text-[11px] text-muted">{{ t("AddSession.LocalShellHint") }}</span>
</span>
<UIcon name="i-lucide-chevron-right" class="size-3.5 shrink-0 text-dimmed" />
</button>
</template>
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">
{{ search.trim() ? "搜索结果" : "最近连接" }}
{{ listTitle }}
</div>
<div v-if="loading" class="grid h-24 place-items-center">
<UIcon name="i-lucide-loader-circle" class="size-4 animate-spin text-muted" />
Expand Down Expand Up @@ -122,7 +130,7 @@ watch(open, (value) => {
v-if="!loading && (search.trim() ? results : recentConnections).length === 0"
class="grid h-24 place-items-center text-xs text-muted"
>
{{ search.trim() ? "未找到匹配资产" : "暂无最近连接" }}
{{ listEmpty }}
</div>
</div>
</div>
Expand Down
Loading
Loading