Skip to content

Commit

Permalink
feat: added support for editing/describing all supported objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeroen Nijhuis committed Jan 15, 2024
1 parent 28a7d12 commit f5aeb7a
Show file tree
Hide file tree
Showing 20 changed files with 467 additions and 68 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"date-fns": "^3.0.6",
"js-yaml": "^4.1.0",
"monaco-editor": "^0.45.0",
"monaco-languageclient": "^7.3.0",
"radix-vue": "^1.2.7",
Expand Down
182 changes: 182 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct SerializableKubeError {

impl From<Error> for SerializableKubeError {
fn from(error: Error) -> Self {
println!("Error: {:?}", error);

match error {
Error::Api(api_error) => {
let code = api_error.code;
Expand Down Expand Up @@ -337,6 +339,176 @@ async fn list_persistentvolumeclaims(
.map_err(|err| SerializableKubeError::from(err));
}

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

return pod_api
.replace(name, &Default::default(), &object)
.await
.map(|pod| pod.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return deployment_api
.replace(name, &Default::default(), &object)
.await
.map(|deployment| deployment.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return job_api
.replace(name, &Default::default(), &object)
.await
.map(|job| job.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return cronjob_api
.replace(name, &Default::default(), &object)
.await
.map(|cronjob| cronjob.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return configmap_api
.replace(name, &Default::default(), &object)
.await
.map(|configmap| configmap.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return secret_api
.replace(name, &Default::default(), &object)
.await
.map(|secret| secret.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return service_api
.replace(name, &Default::default(), &object)
.await
.map(|service| service.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return virtualservice_api
.replace(name, &Default::default(), &object)
.await
.map(|virtualservice| virtualservice.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return ingress_api
.replace(name, &Default::default(), &object)
.await
.map(|ingress| ingress.clone())
.map_err(|err| SerializableKubeError::from(err));
}

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

return pvc_api
.replace(name, &Default::default(), &object)
.await
.map(|pvc: PersistentVolumeClaim| pvc.clone())
.map_err(|err| SerializableKubeError::from(err));
}

struct TerminalSession {
writer: Arc<Mutex<Box<dyn Write + Send>>>,
}
Expand Down Expand Up @@ -459,6 +631,16 @@ fn main() {
list_ingresses,
list_persistentvolumes,
list_persistentvolumeclaims,
replace_pod,
replace_deployment,
replace_job,
replace_cronjob,
replace_configmap,
replace_secret,
replace_service,
replace_virtualservice,
replace_ingress,
replace_persistentvolumeclaim,
create_tty_session,
stop_tty_session,
write_to_pty
Expand Down
21 changes: 19 additions & 2 deletions src/components/TabOrchestrator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {
TabProviderStateKey,
TabProviderCloseTabKey,
TabClosedEvent,
} from "@/providers/TabProvider";
import { injectStrict } from "@/lib/utils";
import TabIcon from "@/components/TabIcon.vue";
Expand Down Expand Up @@ -68,7 +69,18 @@ const onResizeEnd = () => {
window.removeEventListener("mouseup", onResizeEnd);
};
const closeAndSetActiveTab = (id: string) => {
const closeAndSetActiveTab = (id: string, force = false) => {
const canClose = window.dispatchEvent(
new CustomEvent<TabClosedEvent>("TabOrchestrator_TabClosed", {
cancelable: true,
detail: { id },
})
);
if (!canClose && !force) {
return;
}
const indexOfTab = tabs.value.findIndex((tab) => tab.id === id);
closeTab(id);
Expand Down Expand Up @@ -126,7 +138,12 @@ const closeAndSetActiveTab = (id: string) => {
:style="{ height: `${tabHeight}px` }"
>
<keep-alive>
<component :is="activeTab?.component" v-bind="activeTab?.props" />
<component
:is="activeTab?.component"
v-bind="activeTab?.props"
:tabId="activeTab?.id"
@forceClose="closeAndSetActiveTab(activeTab!.id, true)"
/>
</keep-alive>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/tables/pods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const columns: ColumnDef<V1Pod>[] = [
accessorKey: "metadata.name",
header: "Name",
meta: {
class: (row) => {
class: (row: V1Pod) => {
return row.status?.phase === "Pending" ? "text-orange-500" : "";
},
},
Expand Down
45 changes: 45 additions & 0 deletions src/components/tables/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { VirtualService } from "@kubernetes-models/istio/networking.istio.io/v1beta1";
import { KubernetesObject } from "@kubernetes/client-node";

export interface BaseRowAction<T> {
label: string;
}
Expand All @@ -13,3 +16,45 @@ export interface WithHandler<T> extends BaseRowAction<T> {
}

export type RowAction<T> = WithOptions<T> | WithHandler<T>;

export function getDefaultActions<T extends KubernetesObject | VirtualService>(
addTab: any,
context: string
): RowAction<T>[] {
return [
{
label: "Edit",
handler: (row: T) => {
addTab(
`edit_${row.metadata?.name}`,
`${row.metadata?.name}`,
defineAsyncComponent(() => import("@/views/ObjectEditor.vue")),
{
context: context,
namespace: row.metadata?.namespace,
type: row.kind,
name: row.metadata?.name,
},
"edit"
);
},
},
{
label: "Describe",
handler: (row) => {
addTab(
`describe_${row.metadata?.name}`,
`${row.metadata?.name}`,
defineAsyncComponent(() => import("@/views/Describe.vue")),
{
context: context,
namespace: row.metadata?.namespace,
type: row.kind,
name: row.metadata?.name,
},
"describe"
);
},
},
];
}
4 changes: 4 additions & 0 deletions src/providers/TabProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const TabProviderAddTabKey: InjectionKey<
export const TabProviderCloseTabKey: InjectionKey<(id: string) => void> =
Symbol("TabProviderCloseTab");

export type TabClosedEvent = {
id: string;
};

export interface Tab {
id: string;
icon: string;
Expand Down
15 changes: 15 additions & 0 deletions src/services/Kubernetes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ export class Kubernetes {
});
}

static async replaceObject(
context: string,
namespace: string,
type: string,
name: string,
object: unknown
): Promise<V1Pod> {
return invoke(`replace_${type.toLowerCase()}`, {
context: context,
namespace: namespace,
name: name,
object,
});
}

static async deletePod(
context: string,
namespace: string,
Expand Down
11 changes: 9 additions & 2 deletions src/views/ConfigMaps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import { useDataRefresher } from "@/composables/refresher";
const { toast } = useToast();
const configmaps = ref<V1ConfigMap[]>([]);
import { RowAction, getDefaultActions } from "@/components/tables/types";
import { TabProviderAddTabKey } from "@/providers/TabProvider";
const addTab = injectStrict(TabProviderAddTabKey);
const rowActions: RowAction<V1ConfigMap>[] = [
...getDefaultActions<V1ConfigMap>(addTab, context.value),
];
async function getConfigMaps(refresh: boolean = false) {
if (!refresh) {
configmaps.value = [];
Expand Down Expand Up @@ -50,6 +58,5 @@ const { startRefreshing, stopRefreshing } = useDataRefresher(
</script>
x
<template>
<DataTable :data="configmaps" :columns="columns" />
<DataTable :data="configmaps" :columns="columns" :row-actions="rowActions" />
</template>
@/components/tables/configmaps/configmaps
Loading

0 comments on commit f5aeb7a

Please sign in to comment.