Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
gbraad committed Jul 12, 2023
1 parent c2d1f57 commit c276112
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 4 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
cockpit-headscale
=================
Headscale management for Cockpit
================================

!["Prompt"](https://raw.githubusercontent.com/gbraad/assets/gh-pages/icons/prompt-icon-64.png)


Headscale management application for Cockpit


![Screenshot](./docs/screenshot.png)


Authors
-------

| [!["Gerard Braad"](http://gravatar.com/avatar/e466994eea3c2a1672564e45aca844d0.png?s=60)](http://gbraad.nl "Gerard Braad <me@gbraad.nl>") |
|---|
| [@gbraad](https://gbraad.nl/social) |
Binary file added docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 63 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,86 @@
import React from 'react';

import { HeadscaleNode } from './types';
import { Icon } from '@patternfly/react-core';
import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon';
import {
ExpandableRowContent,
Table, Caption, Thead, Tbody, Tr, Th, Td,
SortByDirection,
} from '@patternfly/react-table';

type ApplicationProps = {
}

type ApplicationState = {
Nodes: HeadscaleNode
}

export class Application extends React.Component<ApplicationProps, ApplicationState> {
state: ApplicationState = {
Nodes: null
}

constructor(props: ApplicationProps) {
super(props);

cockpit
.spawn(['headscale', 'nodes', 'list', '-o', 'json'])
.done(content => {
const nodes: HeadscaleNode = JSON.parse(content)
this.setState(state => ({ Nodes: nodes }));
});

}

render() {
return (
<>
Hello
{
this.state.Nodes != null
? <Table
aria-label="Headscale nodes"
variant='compact' borders={false}>
<Caption>Headscale nodes</Caption>
<Thead>
<Tr>
<Th></Th>
<Th>Hostname</Th>
<Th>Name</Th>
<Th>IP addresses</Th>
</Tr>
</Thead>
<Tbody>
{
Object.values(this.state.Nodes)
.map(node => {
return <Node {...node} />
}
)
}
</Tbody>
</Table>

: <p>Loading...</p>
}
</>
)}
}

class Node extends React.Component<HeadscaleNode> {
render() {

return (
<Tr>
<Td>{this.props.online
? <Icon status="success"><CheckCircleIcon /></Icon>
: <Icon status="danger"><ExclamationCircleIcon /></Icon>
}</Td>
<Td>{this.props.name}</Td>
<Td>{this.props.given_name}</Td>
<Td>{this.props.ip_addresses[0]}</Td>
</Tr>);
}
}
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

"tools": {
"index": {
"label": "Template"
"label": "Headscale"
}
}
}
30 changes: 30 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type HeadscaleNode = {
id: number;
machine_key: string;
node_key: string;
disco_key: string;
ip_addresses: string[];
name: string;
user: User;
last_seen: CreatedAt;
last_successful_update: CreatedAt;
expiry: Expiry;
created_at: CreatedAt;
given_name: string;
online?: boolean;
}

export type CreatedAt = {
seconds: number;
nanos: number;
}

export type Expiry = {
seconds: number;
}

export type User = {
id: string;
name: string;
created_at: CreatedAt;
}

0 comments on commit c276112

Please sign in to comment.