npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@testcompany888/backstage-plugin-hetzner

v0.1.1

Published

Backstage frontend plugin for Hetzner Cloud integration.

Readme

Backstage Hetzner Plugin

A plugin for Backstage that integrates with Hetzner Cloud. This plugin allows you to:

  • View an overview of your Hetzner Cloud infrastructure directly in Backstage
  • Inspect details of Hetzner resources (servers, volumes, IPs, etc.) via the Index Page
  • Navigate and filter Hetzner resources as part of your Backstage catalog experience

Table of Contents

Features

The Hetzner Cloud to Backstage plugin needs to give a comprehensive insight into the Hetzner cloud platform. There are two views that provide information:

  • Index Page: Gives an overview of the Hetzner Cloud Platform in general.

    Index Page

  • Resource Card: Gives details about which resources are used and what runs where. This view is available in a tab in each component that uses Hetzner's resources.

    Resource Card

Installation

Frontend Plugin

# From the Backstage root directory
yarn --cwd packages/app add @gluo-nv/backstage-plugin-hetzner

Backend Plugin

This frontend plugin requires the corresponding backend plugin to be installed. Please follow the instructions in the backend plugin directory.

Catalog Backend Module Plugin

This frontend plugin requires the corresponding catalog backend module plugin to be installed. Please follow the instructions in the catalog backend module plugin directory.

Configuration

Frontend Configuration

Add the following to the app-config.yaml to configure the frontend plugin:

app:
  hetzner:
    project:
      title: <Hetzner project title>
      owner: <Hetzner project owner>
      lifecycle: <Hetzner project lifecycle>
  1. Add the plugin to the Backstage application by modifying the packages/app/src/App.tsx:
import { HetznerPage } from '@gluo-nv/backstage-plugin-hetzner';

Add the Hetzner Cloud page to the sidebar

const routes = (
  <FlatRoutes>
    {/* ...other routes*/}
    <Route path="/hetzner" element={<HetznerPage />} />
  </FlatRoutes>
);
  1. ``packages/app/src/components/Root/Root.tsx`

Add the icon import:

import Cloud from '@material-ui/icons/Cloud';
  1. Then add this line just before the </SidebarGroup> line:
export const Root = ({ children }: PropsWithChildren<{}>) => (
  <SidebarPage>
    <Sidebar>
      {/*...*/}
      <SidebarGroup label="Menu" icon={<MenuIcon />}>
        {/* Global nav, not org-specific */}
        {/*...other sidebar items*/}
        <SidebarItem icon={Cloud} to="hetzner" text="Hetzner Cloud" />
        {/* End global nav */}
        <SidebarDivider />
        {/*...*/}
      </SidebarGroup>
      {/*...*/}
    </Sidebar>
    {children}
  </SidebarPage>
);
  1. Add the Hetzner Resources Card to the entity overview page

Add a new file utils.tsx in packages/app/src/components/catalog:

import { Entity } from '@backstage/catalog-model';

export const isHetznerResource = (entity: Entity): boolean => {
  const hetznerData = entity.metadata.annotations?.['hetzner.com/data'];
  try {
    return hetznerData !== undefined && JSON.parse(hetznerData) !== null;
  } catch (e) {
    return false;
  }
};

Add the Resources Card in packages/app/src/components/catalog/EntityPage.tsx

import { EntityHetznerContent } from '@gluo-nv/backstage-plugin-hetzner';
import { isHetznerResource } from './utils';

// Add to the overview content:
const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    {entityWarningContent}
    <Grid item md={6}>
      <EntityAboutCard variant="gridItem" />
    </Grid>
    <Grid item md={6} xs={12}>
      <EntityCatalogGraphCard variant="gridItem" height={400} />
    </Grid>

    {/* Hetzner Cloud Plugin */}
    <EntitySwitch>
      <EntitySwitch.Case
        if={entity => isKind('resource')(entity) && isHetznerResource(entity)}
      >
        <Grid item md={6}>
          <EntityHetznerContent />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
    {/* Hetzner Cloud Plugin */}

    {/*...*/}
  </Grid>
);

Development

Getting Started

  1. Clone the repository
  2. Install dependencies:
yarn install
  1. Run the plugin in isolation
yarn start

Running Tests

Run all tests:

yarn test

Run tests with coverage:

yarn test:coverage

Building

yarn build

Custom test environment

To set up a custom development environment for the Hetzner Cloud plugin, follow these steps:

  1. Create or update the dev/index.tsx file with the following content:
import { createDevApp } from '@backstage/dev-utils';
import {
  hetznerCloudPlugin,
  HetznerCloudPage,
  ResourcesCardPage,
} from '../src/plugin';

// dev
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { CustomHetznerAboutCard } from '../src/components/custom/HetznerEntityAboutCard';

// dev
const mockEntity = {
  apiVersion: 'backstage.io/v1alpha1',
  kind: 'Component',
  metadata: {
    name: 'mock-entity',
    annotations: {
      'hetzner.com/data': JSON.stringify({
        id: 12345,
        name: 'vm-gluo-rke2-runner-1',
        status: 'running',
        created: '2024-11-25T10:04:10+00:00',
        public_net: {
          ipv4: {
            ip: '192.168.0.1',
            dns_ptr: 'example.com',
          },
        },
        datacenter: {
          name: 'nbg1-dc3',
          description: 'Nuremberg 1 virtual DC 3',
          location: {
            city: 'Nuremberg',
            country: 'DE',
          },
        },
        server_type: {
          name: 'cx32',
          description: 'CX32',
          cores: 4,
          memory: 8,
          disk: 80,
        },
        image: {
          name: 'debian-12',
          description: 'Debian 12',
          os_flavor: 'debian',
          os_version: '12',
        },
        outgoing_traffic: 12345,
        ingoing_traffic: 67890,
        included_traffic: 100000,
        resource_type: 'virtual_machine',
      }),
    },
  },
};

createDevApp()
  .registerPlugin(hetznerCloudPlugin)
  .addPage({
    element: <HetznerCloudPage />,
    title: 'Root Page',
    path: '/hetzner-cloud',
  })

  // dev
  .addPage({
    title: 'Resources Card',
    element: (
      <EntityProvider entity={mockEntity}>
        <ResourcesCard />
      </EntityProvider>
    ),
    path: '/resources-card',
  })
  .render();

This file is the entry point for running the plugin in a standalone development environment. It allows you to test the plugin without integrating it into a full Backstage app.

  1. Ensure the src/routes.ts file contains the following:
import { createRouteRef } from '@backstage/core-plugin-api';

export const rootRouteRef = createRouteRef({
  id: 'hetzner-cloud',
});

// dev
export const resourcesCardRouteRef = createRouteRef({
  id: 'resources-card',
});

This file defines the route references for the plugin. Route references are used to link components to specific paths in Backstage.

  1. Ensure the src/plugin.ts file contains the following:
import {
  createPlugin,
  createRoutableExtension,
} from '@backstage/core-plugin-api';

import { resourcesCardRouteRef, rootRouteRef } from './routes';

export const hetznerCloudPlugin = createPlugin({
  id: 'hetzner-cloud',
  routes: {
    root: rootRouteRef,
    resourcesCard: resourcesCardRouteCard, //dev
  },
});

// prod
// export const EntityHetznerCloudContent = hetznerCloudPlugin.provide(
//   createComponentExtension({
//     component: {
//       lazy: () => import ('./components/entity/ResourcesCard').then(m => m.ResourcesCard),
//     }
//   })
// );

export const HetznerCloudPage = hetznerCloudPlugin.provide(
  createRoutableExtension({
    name: 'HetznerCloudPage',
    component: () =>
      import('./components/IndexComponent').then(m => m.IndexComponent),
    mountPoint: rootRouteRef,
  }),
);

// dev
export const ResourcesCardPage = hetznerCloudPlugin.provide(
  createRoutableExtension({
    name: 'ResourcesCardPage',
    component: () =>
      import('./components/entity/ResourcesCard').then(m => m.ResourcesCard),
    mountPoint: resourcesCardRouteRef,
  }),
);

This file defines the main plugin and its extensions. It connects the plugin's routes to their corresponding components.

  1. Ensure the src/index.ts file contains the following:
export {
  hetznerCloudPlugin,
  HetznerCloudPage,
  ResourcesCardPage, //dev
} from './plugin';

This file exports the plugin and its extensions so they can be imported and used in other parts of the application.

Contributing

We welcome contributions to improve this plugin! If you’d like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Submit a pull request with a clear description of your changes.

For major changes, please open an issue first to discuss your ideas.

License

This plugin is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

Attribution

This plugin was created by Gluo NV.
Any use or distribution must include proper attribution to the original author.