feat: scaffold starter template with TypeScript, CRD list view, CI, ArtifactHub
Adds full plugin starter template including: - package.json with @kinvolk/headlamp-plugin devDependency and standard scripts - tsconfig.json extending headlamp plugin config - vitest.config.mts + vitest.setup.ts (jsdom, NODE_ENV=test, localStorage shim) - src/index.tsx: registers sidebar entry and route for ResourceListPage - src/components/ResourceListPage.tsx: placeholder CRD list view with TODO guide - src/components/ResourceListPage.test.tsx: example tests using vi.mock pattern - .github/workflows/ci.yaml: delegates to shared plugin-ci.yaml - .github/workflows/release.yaml: delegates to shared plugin-release.yaml - artifacthub-pkg.yml + artifacthub-repo.yml: ArtifactHub metadata with TODO markers - renovate.json: Mend Renovate config for weekly dependency updates - README.md: complete getting-started guide - CONTRIBUTING.md: local dev, code style, testing, PR process - LICENSE: Apache-2.0 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import ResourceListPage from './ResourceListPage';
|
||||
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
Loader: ({ title }: { title: string }) => <div data-testid="loader">{title}</div>,
|
||||
SectionBox: ({ title, children }: { title: React.ReactNode; children?: React.ReactNode }) => (
|
||||
<section>
|
||||
{title}
|
||||
{children}
|
||||
</section>
|
||||
),
|
||||
SectionHeader: ({ title }: { title: string }) => <h1>{title}</h1>,
|
||||
NameValueTable: ({
|
||||
rows,
|
||||
}: {
|
||||
rows: Array<{ name: React.ReactNode; value: React.ReactNode }>;
|
||||
}) => (
|
||||
<dl>
|
||||
{rows.map((r, i) => (
|
||||
<div key={i}>
|
||||
<dt>{r.name}</dt>
|
||||
<dd>{r.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
),
|
||||
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||
<span data-status={status}>{children}</span>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('ResourceListPage', () => {
|
||||
it('renders the "My Plugin" heading', () => {
|
||||
render(<ResourceListPage />);
|
||||
expect(screen.getByText('My Plugin')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the example resource group row', () => {
|
||||
render(<ResourceListPage />);
|
||||
expect(screen.getByText('Resource Group')).toBeInTheDocument();
|
||||
expect(screen.getByText('your.group.io/v1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the example Kind row', () => {
|
||||
render(<ResourceListPage />);
|
||||
expect(screen.getByText('Kind')).toBeInTheDocument();
|
||||
expect(screen.getByText('YourCustomResource')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* ResourceListPage — placeholder resource list view.
|
||||
*
|
||||
* TODO: Replace this component with a real CRD list view. Typical approaches:
|
||||
*
|
||||
* 1. Use K8s.ResourceClasses to build a typed resource class for your CRD:
|
||||
* ```ts
|
||||
* const MyResource = K8s.makeKubeObject<MyResourceSpec>('MyResource');
|
||||
* MyResource.apiEndpoint = K8s.ApiProxy.apiFactory('your.group.io', 'v1', 'yourresources');
|
||||
* ```
|
||||
*
|
||||
* 2. Then use the ResourceListView or SectionBox + a data-fetching hook to list instances.
|
||||
*
|
||||
* 3. Remove the placeholder NameValueTable below and replace with your real UI.
|
||||
*
|
||||
* See https://headlamp.dev/docs/latest/development/plugins/functionality/ for the full SDK guide.
|
||||
*/
|
||||
|
||||
import {
|
||||
NameValueTable,
|
||||
SectionBox,
|
||||
SectionHeader,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
|
||||
// TODO: Replace this with your CRD list using K8s.ResourceClasses or K8s.makeKubeObject
|
||||
export default function ResourceListPage() {
|
||||
const placeholderRows = [
|
||||
{ name: 'Resource Group', value: 'your.group.io/v1' },
|
||||
{ name: 'Kind', value: 'YourCustomResource' },
|
||||
{ name: 'Namespace', value: 'default (or cluster-scoped)' },
|
||||
{ name: 'Description', value: 'Replace this view with your real CRD list' },
|
||||
];
|
||||
|
||||
return (
|
||||
<SectionBox title={<SectionHeader title="My Plugin" />}>
|
||||
<NameValueTable rows={placeholderRows} />
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* my-headlamp-plugin — entry point.
|
||||
*
|
||||
* Registers sidebar entries and routes for this Headlamp plugin.
|
||||
* Replace the sidebar labels, URLs, and route component with your own.
|
||||
*/
|
||||
|
||||
import { registerRoute, registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
|
||||
import React from 'react';
|
||||
import ResourceListPage from './components/ResourceListPage';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sidebar entries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Top-level sidebar section (parent = null creates a new group in the nav)
|
||||
registerSidebarEntry({
|
||||
parent: null,
|
||||
name: 'my-plugin',
|
||||
label: 'My Plugin',
|
||||
url: '/my-plugin',
|
||||
icon: 'mdi:kubernetes',
|
||||
});
|
||||
|
||||
// Child entry — shown nested under the parent section above
|
||||
registerSidebarEntry({
|
||||
parent: 'my-plugin',
|
||||
name: 'my-plugin-list',
|
||||
label: 'Resources',
|
||||
url: '/my-plugin',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Routes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
registerRoute({
|
||||
path: '/my-plugin',
|
||||
sidebar: 'my-plugin-list',
|
||||
name: 'my-plugin-list',
|
||||
exact: true,
|
||||
component: () => <ResourceListPage />,
|
||||
});
|
||||
Reference in New Issue
Block a user