Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add detail view for secrets and easy decoding #47

Merged
merged 6 commits into from
Jan 10, 2025
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
15 changes: 15 additions & 0 deletions src-tauri/src/kubernetes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ pub mod client {
.map_err(|err| SerializableKubeError::from(err));
}

#[tauri::command]
pub async fn get_pod_metric(
context: &str,
namespace: &str,
name: &str,
) -> Result<PodMetrics, SerializableKubeError> {
let client = client_with_context(context).await?;
let metrics_api: Api<PodMetrics> = Api::namespaced(client, namespace);

return metrics_api
.get(name)
.await
.map_err(|err| SerializableKubeError::from(err));
}

#[tauri::command]
pub async fn get_pod(
context: &str,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ fn main() {
kubernetes::client::replace_ingress,
kubernetes::client::replace_persistentvolumeclaim,
kubernetes::client::get_pod_metrics,
kubernetes::client::get_pod_metric,
kubernetes::client::trigger_cronjob,
shell::tty::create_tty_session,
shell::tty::stop_tty_session,
Expand Down
18 changes: 14 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import AppLayout from "@/components/AppLayout.vue";
import Navigation from "@/components/Navigation.vue";
import RouterViewport from "@/components/RouterViewport.vue";
import SidePanel from "@/components/SidePanel.vue";
import Toaster from "@/components/ui/toast/Toaster.vue";
import CommandPalette from "./components/CommandPalette.vue";
import SettingsContextProvider from "./providers/SettingsContextProvider";
Expand All @@ -10,11 +11,16 @@ import ColorSchemeProvider from "./providers/ColorSchemeProvider";
import KubeContextProvider from "./providers/KubeContextProvider";
import PortForwardingProvider from "./providers/PortForwardingProvider";
import CommandPaletteProvider from "./providers/CommandPaletteProvider";
import TabProvider from "./providers/TabProvider";
import PanelProvider from "./providers/PanelProvider";
import DialogProvider from "./providers/DialogProvider";
import DialogHandler from "./components/DialogHandler.vue";
import UpdateHandler from "./components/UpdateHandler.vue";
import WhatsNew from "./components/WhatsNew.vue";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { type as getOsType } from "@tauri-apps/plugin-os";

const osType = ref(getOsType());
Expand All @@ -32,17 +38,21 @@ const osType = ref(getOsType());
<DialogProvider>
<KubeContextProvider>
<PortForwardingProvider>
<TabProvider>
<PanelProvider>
<CommandPaletteProvider>
<Navigation />
<RouterViewport />
<ResizablePanelGroup direction="horizontal">
<ResizablePanel><RouterViewport /></ResizablePanel>
<ResizableHandle />
<SidePanel />
</ResizablePanelGroup>
<Toaster />
<CommandPalette />
<DialogHandler />
<UpdateHandler />
<WhatsNew />
</CommandPaletteProvider>
</TabProvider>
</PanelProvider>
</PortForwardingProvider>
</KubeContextProvider>
</DialogProvider>
Expand Down
1 change: 1 addition & 0 deletions src/assets/icons/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/icons/cpu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/icons/eye_close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/icons/eye_open.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 16 additions & 15 deletions src/components/RouterViewport.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<script setup lang="ts">
import { KubeContextStateKey } from "@/providers/KubeContextProvider";
import { injectStrict } from "@/lib/utils";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import TabOrchestrator from "@/components/TabOrchestrator.vue";
import { useRoute } from "vue-router";
import NoContext from "@/views/NoContext.vue";
Expand All @@ -10,19 +14,16 @@ const { context } = injectStrict(KubeContextStateKey);
const route = useRoute();
</script>
<template>
<div class="flex flex-col max-h-screen relative router-viewport">
<ScrollArea
class="w-full flex flex-grow border-l border-border bg-background"
>
<NoContext v-if="route.meta.requiresContext && context == ''" />
<router-view v-else />
<ScrollBar orientation="horizontal" />
</ScrollArea>
<TabOrchestrator />
<div
class="flex flex-col h-full relative w-full border-l border-border bg-background"
>
<ResizablePanelGroup direction="vertical">
<ResizablePanel>
<NoContext v-if="route.meta.requiresContext && context == ''" />
<router-view v-else />
</ResizablePanel>
<ResizableHandle />
<TabOrchestrator />
</ResizablePanelGroup>
</div>
</template>
<style>
.router-viewport {
width: calc(100vw - 200px);
}
</style>
46 changes: 46 additions & 0 deletions src/components/SidePanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import {
PanelProviderStateKey,
PanelProviderSetSidePanelComponentKey,
} from "@/providers/PanelProvider";
import { injectStrict } from "@/lib/utils";
import { ResizablePanel } from "@/components/ui/resizable";
import CloseIcon from "@/assets/icons/close.svg";
import { useRoute } from "vue-router";

const route = useRoute();

const { sidePanel } = injectStrict(PanelProviderStateKey);
const setSidePanelComponent = injectStrict(
PanelProviderSetSidePanelComponentKey
);

const icon = defineAsyncComponent(() =>
import(`@/assets/icons/${sidePanel.value?.icon}.svg`).catch(
() => import("@/assets/icons/k8s.svg")
)
);

watch(route, () => {
setSidePanelComponent(null);
});
</script>

<template>
<ResizablePanel
v-if="sidePanel !== null"
:default-size="30"
class="max-h-screen !overflow-y-auto"
>
<div class="bg-background p-4 flex justify-between items-center border-b">
<div class="flex items-center space-x-2">
<component v-if="sidePanel.icon" :is="icon" class="h-4" />
<span class="">{{ sidePanel.title }}</span>
</div>
<button @click="setSidePanelComponent(null)">
<CloseIcon class="w-4 h-4" />
</button>
</div>
<component :is="sidePanel.component" v-bind="sidePanel.props" />
</ResizablePanel>
</template>
147 changes: 57 additions & 90 deletions src/components/TabOrchestrator.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
<script setup lang="ts">
import {
TabProviderStateKey,
TabProviderCloseTabKey,
PanelProviderStateKey,
PanelProviderCloseTabKey,
TabClosedEvent,
} from "@/providers/TabProvider";
} from "@/providers/PanelProvider";
import { injectStrict } from "@/lib/utils";
import TabIcon from "@/components/TabIcon.vue";
import Expand from "@/assets/icons/expand.svg";
import Close from "@/assets/icons/close.svg";
import { SettingsContextStateKey } from "@/providers/SettingsContextProvider";

const { tabs, activeTabId } = injectStrict(TabProviderStateKey);
import { ResizablePanel } from "@/components/ui/resizable";

const { tabs, activeTabId } = injectStrict(PanelProviderStateKey);
const { settings } = injectStrict(SettingsContextStateKey);
const closeTab = injectStrict(TabProviderCloseTabKey);
const closeTab = injectStrict(PanelProviderCloseTabKey);

const state = reactive({
open: true,
resizing: false,
resizeStartPositionY: 0,
rerenderKey: 0,
});

const tabHeight = computed(() => {
return settings.value.tabProvider.height >= 30
? settings.value.tabProvider.height
: 30;
});

const activeTab = computed(() => {
return tabs.value.find((tab) => tab.id === activeTabId.value);
});
Expand All @@ -39,36 +33,6 @@ const setActiveTab = (id: string) => {
}
};

const onResizeStart = (e: MouseEvent) => {
if (!state.open) {
return;
}

state.resizing = true;
state.resizeStartPositionY = e.clientY;

window.addEventListener("mousemove", onResizing);
window.addEventListener("mouseup", onResizeEnd);
};

const onResizing = (e: MouseEvent) => {
if (!state.resizing) {
return;
}

const delta = e.clientY - state.resizeStartPositionY;
settings.value.tabProvider.height -= delta;
state.resizeStartPositionY = e.clientY;

window.dispatchEvent(new Event("TabOrchestrator_Resized"));
};

const onResizeEnd = () => {
state.resizing = false;
window.removeEventListener("mousemove", onResizing);
window.removeEventListener("mouseup", onResizeEnd);
};

const closeAndSetActiveTab = (id: string, force = false) => {
const canClose = window.dispatchEvent(
new CustomEvent<TabClosedEvent>("TabOrchestrator_TabClosed", {
Expand All @@ -92,63 +56,66 @@ const closeAndSetActiveTab = (id: string, force = false) => {
}
}
};

const handleResize = (size: number) => {
settings.value.PanelProvider.height = size;

window.dispatchEvent(new Event("TabOrchestrator_Resized"));
};
</script>
<template>
<div
@keydown.stop="() => {}"
class="relative border-t border-l bg-background"
<ResizablePanel
v-if="tabs.length > 0"
:defaultSize="settings.PanelProvider.height"
@resize="handleResize"
>
<div
class="absolute w-full h-2 border-t border-transparent"
:class="{ 'hover:border-white cursor-ns-resize': state.open }"
@mousedown="onResizeStart"
></div>
<div class="flex items-center mb-0 text-xs py-1 px-1">
<div class="flex space-x-3">
<div
class="group relative flex items-center py-1 px-2 rounded cursor-pointer max-w-[200px] truncate hover:bg-border"
:class="{
'bg-border': activeTabId === tab.id,
'text-gray-400': activeTabId !== tab.id,
}"
v-for="tab in tabs"
@click="setActiveTab(tab.id)"
:title="tab.title"
>
<tab-icon :name="tab.icon" class="mr-1" />
<span class="truncate">{{ tab.title }}</span>
class="flex h-full flex-col relative border-t border-l bg-background"
@keydown.stop="() => {}"
>
<div class="flex items-center mb-0 text-xs py-1 px-1">
<div class="flex space-x-3">
<div
@click="closeAndSetActiveTab(tab.id)"
class="hidden group-hover:block absolute right-1 p-0.5 rounded-sm bg-opacity-50 bg-accent hover:bg-accent text-foreground"
v-for="tab in tabs"
:key="tab.id"
:title="tab.title"
class="group relative flex items-center py-1 px-2 rounded cursor-pointer max-w-[200px] truncate hover:bg-border"
:class="{
'bg-border': activeTabId === tab.id,
'text-gray-400': activeTabId !== tab.id,
}"
@click="setActiveTab(tab.id)"
>
<Close class="h-3" />
<tab-icon :name="tab.icon" class="mr-1" />
<span class="truncate">{{ tab.title }}</span>
<div
@click="closeAndSetActiveTab(tab.id)"
class="hidden group-hover:block absolute right-1 p-0.5 rounded-sm bg-opacity-50 bg-accent hover:bg-accent text-foreground"
>
<Close class="h-3" />
</div>
</div>
</div>
<div
class="ml-auto p-1 rounded cursor-pointer hover:bg-border"
@click="state.open = !state.open"
>
<Expand
class="text-white h-3"
:class="{ 'rotate-90': !state.open, 'rotate-270': state.open }"
/>
</div>
</div>
<div
class="ml-auto p-1 rounded cursor-pointer hover:bg-border"
@click="state.open = !state.open"
>
<Expand
class="text-white h-3"
:class="{ 'rotate-90': !state.open, 'rotate-270': state.open }"
/>
<div class="relative flex-grow p-2 overflow-auto" v-show="state.open">
<keep-alive>
<component
:is="activeTab?.component"
v-bind="activeTab?.props"
:tabId="activeTab?.id"
@forceClose="closeAndSetActiveTab(activeTab!.id, true)"
/>
</keep-alive>
</div>
</div>
<div
class="relative p-2"
v-show="state.open"
:style="{ height: `${tabHeight}px` }"
>
<keep-alive>
<component
:is="activeTab?.component"
v-bind="activeTab?.props"
:tabId="activeTab?.id"
@forceClose="closeAndSetActiveTab(activeTab!.id, true)"
/>
</keep-alive>
</div>
</div>
</ResizablePanel>
</template>
Loading
Loading