Skip to content

Commit 7acfd33

Browse files
committed
feat: version footer and fix CI version handlng
1 parent 64b874b commit 7acfd33

File tree

11 files changed

+111
-60
lines changed

11 files changed

+111
-60
lines changed

.github/workflows/docker.yml

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,6 @@ on:
99
type: string
1010

1111
jobs:
12-
debug:
13-
name: Debugging information
14-
runs-on: ubuntu-latest
15-
steps:
16-
- name: Checkout code
17-
uses: actions/checkout@v4
18-
- name: List repository root contents
19-
run: |
20-
echo "Repository root contents:"
21-
ls -la
22-
echo "Looking for admin directory:"
23-
ls -la admin/ || echo "admin directory not found"
24-
- name: Print GitHub context
25-
run: echo "${{ toJson(github) }}"
26-
- name: Print workflow inputs
27-
run: echo "${{ toJson(inputs) }}"
2812
check_authorization:
2913
name: Check authorization to publish new Docker image
3014
runs-on: ubuntu-latest
@@ -54,9 +38,7 @@ jobs:
5438
- name: Build and push
5539
uses: docker/build-push-action@v5
5640
with:
57-
context: ./admin
58-
file: ./admin/Dockerfile
5941
push: true
6042
tags: |
61-
ghcr.io/crosstalk-solutions/project-nomad-admin:${{ inputs.version }}
62-
ghcr.io/crosstalk-solutions/project-nomad-admin:latest
43+
ghcr.io/crosstalk-solutions/project-nomad:${{ inputs.version }}
44+
ghcr.io/crosstalk-solutions/project-nomad:latest

admin/Dockerfile renamed to Dockerfile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ RUN apk add --no-cache bash curl
66
# All deps stage
77
FROM base AS deps
88
WORKDIR /app
9-
ADD package.json package-lock.json ./
9+
ADD admin/package.json admin/package-lock.json ./
1010
RUN npm ci
1111

1212
# Production only deps stage
1313
FROM base AS production-deps
1414
WORKDIR /app
15-
ADD package.json package-lock.json ./
15+
ADD admin/package.json admin/package-lock.json ./
1616
RUN npm ci --omit=dev
1717

1818
# Build stage
1919
FROM base AS build
2020
WORKDIR /app
2121
COPY --from=deps /app/node_modules /app/node_modules
22-
ADD . .
22+
ADD admin/ ./
2323
RUN node ace build
2424

2525
# Production stage
@@ -28,6 +28,8 @@ ENV NODE_ENV=production
2828
WORKDIR /app
2929
COPY --from=production-deps /app/node_modules /app/node_modules
3030
COPY --from=build /app/build /app
31-
COPY ./docs /app/docs
31+
# Copy root package.json for version info
32+
COPY package.json /app/version.json
33+
COPY admin/docs /app/docs
3234
EXPOSE 8080
3335
CMD ["node", "./bin/server.js"]

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ To test internet connectivity, N.O.M.A.D. attempts to make a request to Cloudfla
6363
## About Security
6464
By design, Project N.O.M.A.D. is intended to be open and available without hurdles - it includes no authentication. If you decide to connect your device to a local network after install (e.g. for allowing other devices to access it's resources), you can block/open ports to control which services are exposed.
6565

66-
# Helper Scripts
66+
## Versioning
67+
This project uses semantic versioning. The version is managed in the root `package.json`
68+
and automatically updated by semantic-release. For simplicity's sake, the "project-nomad" container
69+
uses the same version defined there instead of the version in `admin/package.json` (stays at 0.0.0), as it's the only container derived from the code.
70+
71+
## Helper Scripts
6772
Once installed, Project N.O.M.A.D. has a few helper scripts should you ever need to troubleshoot issues or perform maintenance that can't be done through the Command Center. All of these scripts are found in Project N.O.M.A.D.'s install directory, `/opt/project-nomad`
6873

6974
###
Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,76 @@
1-
import Service from "#models/service"
2-
import { inject } from "@adonisjs/core";
3-
import { DockerService } from "#services/docker_service";
4-
import { ServiceSlim } from "../../types/services.js";
5-
import logger from "@adonisjs/core/services/logger";
6-
import si from 'systeminformation';
7-
import { SystemInformationResponse } from "../../types/system.js";
1+
import Service from '#models/service'
2+
import { inject } from '@adonisjs/core'
3+
import { DockerService } from '#services/docker_service'
4+
import { ServiceSlim } from '../../types/services.js'
5+
import logger from '@adonisjs/core/services/logger'
6+
import si from 'systeminformation'
7+
import { SystemInformationResponse } from '../../types/system.js'
8+
import { readFileSync } from 'fs'
9+
import { join } from 'path'
810

911
@inject()
1012
export class SystemService {
11-
constructor(
12-
private dockerService: DockerService
13-
) { }
14-
async getServices({
15-
installedOnly = true,
16-
}: {
17-
installedOnly?: boolean
18-
}): Promise<ServiceSlim[]> {
19-
const query = Service.query().orderBy('friendly_name', 'asc').select('id', 'service_name', 'installed', 'ui_location', 'friendly_name', 'description').where('is_dependency_service', false)
13+
private static appVersion: string | null = null
14+
15+
constructor(private dockerService: DockerService) {}
16+
17+
async getServices({ installedOnly = true }: { installedOnly?: boolean }): Promise<ServiceSlim[]> {
18+
const query = Service.query()
19+
.orderBy('friendly_name', 'asc')
20+
.select('id', 'service_name', 'installed', 'ui_location', 'friendly_name', 'description')
21+
.where('is_dependency_service', false)
2022
if (installedOnly) {
21-
query.where('installed', true);
23+
query.where('installed', true)
2224
}
2325

24-
const services = await query;
26+
const services = await query
2527
if (!services || services.length === 0) {
26-
return [];
28+
return []
2729
}
2830

29-
const statuses = await this.dockerService.getServicesStatus();
31+
const statuses = await this.dockerService.getServicesStatus()
3032

31-
const toReturn: ServiceSlim[] = [];
33+
const toReturn: ServiceSlim[] = []
3234

3335
for (const service of services) {
34-
const status = statuses.find(s => s.service_name === service.service_name);
36+
const status = statuses.find((s) => s.service_name === service.service_name)
3537
toReturn.push({
3638
id: service.id,
3739
service_name: service.service_name,
3840
friendly_name: service.friendly_name,
3941
description: service.description,
4042
installed: service.installed,
4143
status: status ? status.status : 'unknown',
42-
ui_location: service.ui_location || ''
43-
});
44+
ui_location: service.ui_location || '',
45+
})
4446
}
4547

46-
return toReturn;
48+
return toReturn
49+
}
50+
51+
static getAppVersion(): string {
52+
try {
53+
if (this.appVersion) {
54+
return this.appVersion
55+
}
56+
57+
// Return 'dev' for development environment (version.json won't exist)
58+
if (process.env.NODE_ENV === 'development') {
59+
this.appVersion = 'dev'
60+
return 'dev'
61+
}
62+
63+
const packageJson = readFileSync(join(process.cwd(), 'version.json'), 'utf-8')
64+
const packageData = JSON.parse(packageJson)
65+
66+
const version = packageData.version || '0.0.0'
4767

68+
this.appVersion = version
69+
return version
70+
} catch (error) {
71+
logger.error('Error getting app version:', error)
72+
return '0.0.0'
73+
}
4874
}
4975

5076
async getSystemInfo(): Promise<SystemInformationResponse | undefined> {
@@ -53,18 +79,18 @@ export class SystemService {
5379
si.cpu(),
5480
si.mem(),
5581
si.osInfo(),
56-
si.diskLayout()
57-
]);;
82+
si.diskLayout(),
83+
])
5884

5985
return {
6086
cpu,
6187
mem,
6288
os,
63-
disk
64-
};
89+
disk,
90+
}
6591
} catch (error) {
66-
logger.error('Error getting system info:', error);
67-
return undefined;
92+
logger.error('Error getting system info:', error)
93+
return undefined
6894
}
6995
}
70-
}
96+
}

admin/config/inertia.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SystemService } from '#services/system_service'
12
import { defineConfig } from '@adonisjs/inertia'
23
import type { InferSharedProps } from '@adonisjs/inertia/types'
34

@@ -11,7 +12,7 @@ const inertiaConfig = defineConfig({
1112
* Data that should be shared with all rendered pages
1213
*/
1314
sharedData: {
14-
// user: (ctx) => ctx.inertia.always(() => ctx.auth.user),
15+
appVersion: () => SystemService.getAppVersion(),
1516
},
1617

1718
/**
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { usePage } from '@inertiajs/react'
2+
3+
export default function Footer() {
4+
const { appVersion } = usePage().props as unknown as { appVersion: string }
5+
return (
6+
<footer className="">
7+
<div className="flex justify-center border-t border-gray-900/10 py-4">
8+
<p className="text-sm/6 text-gray-600">
9+
Project N.O.M.A.D. Command Center v{appVersion}
10+
</p>
11+
</div>
12+
</footer>
13+
)
14+
}

admin/inertia/components/StyledSidebar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessu
33
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
44
import classNames from '~/lib/classNames'
55
import { IconArrowLeft } from '@tabler/icons-react'
6+
import { usePage } from '@inertiajs/react'
7+
import { UsePageProps } from '../../types/system'
68

79
type SidebarItem = {
810
name: string
@@ -19,6 +21,7 @@ interface StyledSidebarProps {
1921

2022
const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
2123
const [sidebarOpen, setSidebarOpen] = useState(false)
24+
const { appVersion } = usePage().props as unknown as UsePageProps
2225

2326
const currentPath = useMemo(() => {
2427
if (typeof window === 'undefined') return ''
@@ -72,6 +75,9 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
7275
</li>
7376
</ul>
7477
</nav>
78+
<div className="mb-4 text-center text-sm text-gray-600">
79+
<p>Project N.O.M.A.D. Command Center v{appVersion}</p>
80+
</div>
7581
</div>
7682
)
7783
}

admin/inertia/css/app.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
--color-desert-green-light: #babaaa;
66
--color-desert-green: #424420;
77
--color-desert-orange: #a84a12;
8-
--color-desert-sand: #f7eedc
8+
--color-desert-sand: #f7eedc;
9+
/* --color-desert-sand: #E2DAC2; */
910
}
1011

1112
body {

admin/inertia/layouts/AppLayout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Footer from "~/components/Footer";
2+
13
export default function AppLayout({ children }: { children: React.ReactNode }) {
24
return (
35
<div className="min-h-screen flex flex-col">
@@ -17,6 +19,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
1719
className="text-desert-orange font-semibold text-sm italic"
1820
>A project by Crosstalk Solutions</p>
1921
</div> */}
22+
<Footer />
2023
</div>
2124
)
2225
}

admin/inertia/layouts/SettingsLayout.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import { getServiceLink } from '~/lib/navigation'
1111
const navigation = [
1212
{ name: 'Apps', href: '/settings/apps', icon: CommandLineIcon, current: false },
1313
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
14-
{ name: 'Service Logs & Metrics', href: getServiceLink('9999'), icon: IconDashboard, current: false, target: '_blank' },
14+
{
15+
name: 'Service Logs & Metrics',
16+
href: getServiceLink('9999'),
17+
icon: IconDashboard,
18+
current: false,
19+
target: '_blank',
20+
},
1521
{ name: 'ZIM Manager', href: '/settings/zim', icon: FolderIcon, current: false },
1622
{
1723
name: 'Zim Remote Explorer',

0 commit comments

Comments
 (0)