mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
feat: version footer and fix CI version handlng
This commit is contained in:
parent
64b874b1f3
commit
7acfd33d5c
22
.github/workflows/docker.yml
vendored
22
.github/workflows/docker.yml
vendored
|
|
@ -9,22 +9,6 @@ on:
|
|||
type: string
|
||||
|
||||
jobs:
|
||||
debug:
|
||||
name: Debugging information
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: List repository root contents
|
||||
run: |
|
||||
echo "Repository root contents:"
|
||||
ls -la
|
||||
echo "Looking for admin directory:"
|
||||
ls -la admin/ || echo "admin directory not found"
|
||||
- name: Print GitHub context
|
||||
run: echo "${{ toJson(github) }}"
|
||||
- name: Print workflow inputs
|
||||
run: echo "${{ toJson(inputs) }}"
|
||||
check_authorization:
|
||||
name: Check authorization to publish new Docker image
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -54,9 +38,7 @@ jobs:
|
|||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./admin
|
||||
file: ./admin/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/crosstalk-solutions/project-nomad-admin:${{ inputs.version }}
|
||||
ghcr.io/crosstalk-solutions/project-nomad-admin:latest
|
||||
ghcr.io/crosstalk-solutions/project-nomad:${{ inputs.version }}
|
||||
ghcr.io/crosstalk-solutions/project-nomad:latest
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@ RUN apk add --no-cache bash curl
|
|||
# All deps stage
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
ADD package.json package-lock.json ./
|
||||
ADD admin/package.json admin/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Production only deps stage
|
||||
FROM base AS production-deps
|
||||
WORKDIR /app
|
||||
ADD package.json package-lock.json ./
|
||||
ADD admin/package.json admin/package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
# Build stage
|
||||
FROM base AS build
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules /app/node_modules
|
||||
ADD . .
|
||||
ADD admin/ ./
|
||||
RUN node ace build
|
||||
|
||||
# Production stage
|
||||
|
|
@ -28,6 +28,8 @@ ENV NODE_ENV=production
|
|||
WORKDIR /app
|
||||
COPY --from=production-deps /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/build /app
|
||||
COPY ./docs /app/docs
|
||||
# Copy root package.json for version info
|
||||
COPY package.json /app/version.json
|
||||
COPY admin/docs /app/docs
|
||||
EXPOSE 8080
|
||||
CMD ["node", "./bin/server.js"]
|
||||
|
|
@ -63,7 +63,12 @@ To test internet connectivity, N.O.M.A.D. attempts to make a request to Cloudfla
|
|||
## About Security
|
||||
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.
|
||||
|
||||
# Helper Scripts
|
||||
## Versioning
|
||||
This project uses semantic versioning. The version is managed in the root `package.json`
|
||||
and automatically updated by semantic-release. For simplicity's sake, the "project-nomad" container
|
||||
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.
|
||||
|
||||
## Helper Scripts
|
||||
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`
|
||||
|
||||
###
|
||||
|
|
|
|||
|
|
@ -1,37 +1,39 @@
|
|||
import Service from "#models/service"
|
||||
import { inject } from "@adonisjs/core";
|
||||
import { DockerService } from "#services/docker_service";
|
||||
import { ServiceSlim } from "../../types/services.js";
|
||||
import logger from "@adonisjs/core/services/logger";
|
||||
import si from 'systeminformation';
|
||||
import { SystemInformationResponse } from "../../types/system.js";
|
||||
import Service from '#models/service'
|
||||
import { inject } from '@adonisjs/core'
|
||||
import { DockerService } from '#services/docker_service'
|
||||
import { ServiceSlim } from '../../types/services.js'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import si from 'systeminformation'
|
||||
import { SystemInformationResponse } from '../../types/system.js'
|
||||
import { readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
@inject()
|
||||
export class SystemService {
|
||||
constructor(
|
||||
private dockerService: DockerService
|
||||
) { }
|
||||
async getServices({
|
||||
installedOnly = true,
|
||||
}: {
|
||||
installedOnly?: boolean
|
||||
}): Promise<ServiceSlim[]> {
|
||||
const query = Service.query().orderBy('friendly_name', 'asc').select('id', 'service_name', 'installed', 'ui_location', 'friendly_name', 'description').where('is_dependency_service', false)
|
||||
private static appVersion: string | null = null
|
||||
|
||||
constructor(private dockerService: DockerService) {}
|
||||
|
||||
async getServices({ installedOnly = true }: { installedOnly?: boolean }): Promise<ServiceSlim[]> {
|
||||
const query = Service.query()
|
||||
.orderBy('friendly_name', 'asc')
|
||||
.select('id', 'service_name', 'installed', 'ui_location', 'friendly_name', 'description')
|
||||
.where('is_dependency_service', false)
|
||||
if (installedOnly) {
|
||||
query.where('installed', true);
|
||||
query.where('installed', true)
|
||||
}
|
||||
|
||||
const services = await query;
|
||||
const services = await query
|
||||
if (!services || services.length === 0) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
const statuses = await this.dockerService.getServicesStatus();
|
||||
const statuses = await this.dockerService.getServicesStatus()
|
||||
|
||||
const toReturn: ServiceSlim[] = [];
|
||||
const toReturn: ServiceSlim[] = []
|
||||
|
||||
for (const service of services) {
|
||||
const status = statuses.find(s => s.service_name === service.service_name);
|
||||
const status = statuses.find((s) => s.service_name === service.service_name)
|
||||
toReturn.push({
|
||||
id: service.id,
|
||||
service_name: service.service_name,
|
||||
|
|
@ -39,12 +41,36 @@ export class SystemService {
|
|||
description: service.description,
|
||||
installed: service.installed,
|
||||
status: status ? status.status : 'unknown',
|
||||
ui_location: service.ui_location || ''
|
||||
});
|
||||
ui_location: service.ui_location || '',
|
||||
})
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
return toReturn
|
||||
}
|
||||
|
||||
static getAppVersion(): string {
|
||||
try {
|
||||
if (this.appVersion) {
|
||||
return this.appVersion
|
||||
}
|
||||
|
||||
// Return 'dev' for development environment (version.json won't exist)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.appVersion = 'dev'
|
||||
return 'dev'
|
||||
}
|
||||
|
||||
const packageJson = readFileSync(join(process.cwd(), 'version.json'), 'utf-8')
|
||||
const packageData = JSON.parse(packageJson)
|
||||
|
||||
const version = packageData.version || '0.0.0'
|
||||
|
||||
this.appVersion = version
|
||||
return version
|
||||
} catch (error) {
|
||||
logger.error('Error getting app version:', error)
|
||||
return '0.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
async getSystemInfo(): Promise<SystemInformationResponse | undefined> {
|
||||
|
|
@ -53,18 +79,18 @@ export class SystemService {
|
|||
si.cpu(),
|
||||
si.mem(),
|
||||
si.osInfo(),
|
||||
si.diskLayout()
|
||||
]);;
|
||||
si.diskLayout(),
|
||||
])
|
||||
|
||||
return {
|
||||
cpu,
|
||||
mem,
|
||||
os,
|
||||
disk
|
||||
};
|
||||
disk,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error getting system info:', error);
|
||||
return undefined;
|
||||
logger.error('Error getting system info:', error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { SystemService } from '#services/system_service'
|
||||
import { defineConfig } from '@adonisjs/inertia'
|
||||
import type { InferSharedProps } from '@adonisjs/inertia/types'
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ const inertiaConfig = defineConfig({
|
|||
* Data that should be shared with all rendered pages
|
||||
*/
|
||||
sharedData: {
|
||||
// user: (ctx) => ctx.inertia.always(() => ctx.auth.user),
|
||||
appVersion: () => SystemService.getAppVersion(),
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
14
admin/inertia/components/Footer.tsx
Normal file
14
admin/inertia/components/Footer.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { usePage } from '@inertiajs/react'
|
||||
|
||||
export default function Footer() {
|
||||
const { appVersion } = usePage().props as unknown as { appVersion: string }
|
||||
return (
|
||||
<footer className="">
|
||||
<div className="flex justify-center border-t border-gray-900/10 py-4">
|
||||
<p className="text-sm/6 text-gray-600">
|
||||
Project N.O.M.A.D. Command Center v{appVersion}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ import { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessu
|
|||
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import classNames from '~/lib/classNames'
|
||||
import { IconArrowLeft } from '@tabler/icons-react'
|
||||
import { usePage } from '@inertiajs/react'
|
||||
import { UsePageProps } from '../../types/system'
|
||||
|
||||
type SidebarItem = {
|
||||
name: string
|
||||
|
|
@ -19,6 +21,7 @@ interface StyledSidebarProps {
|
|||
|
||||
const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const { appVersion } = usePage().props as unknown as UsePageProps
|
||||
|
||||
const currentPath = useMemo(() => {
|
||||
if (typeof window === 'undefined') return ''
|
||||
|
|
@ -72,6 +75,9 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
|
|||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="mb-4 text-center text-sm text-gray-600">
|
||||
<p>Project N.O.M.A.D. Command Center v{appVersion}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
--color-desert-green-light: #babaaa;
|
||||
--color-desert-green: #424420;
|
||||
--color-desert-orange: #a84a12;
|
||||
--color-desert-sand: #f7eedc
|
||||
--color-desert-sand: #f7eedc;
|
||||
/* --color-desert-sand: #E2DAC2; */
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import Footer from "~/components/Footer";
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
|
|
@ -17,6 +19,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|||
className="text-desert-orange font-semibold text-sm italic"
|
||||
>A project by Crosstalk Solutions</p>
|
||||
</div> */}
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,13 @@ import { getServiceLink } from '~/lib/navigation'
|
|||
const navigation = [
|
||||
{ name: 'Apps', href: '/settings/apps', icon: CommandLineIcon, current: false },
|
||||
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
|
||||
{ name: 'Service Logs & Metrics', href: getServiceLink('9999'), icon: IconDashboard, current: false, target: '_blank' },
|
||||
{
|
||||
name: 'Service Logs & Metrics',
|
||||
href: getServiceLink('9999'),
|
||||
icon: IconDashboard,
|
||||
current: false,
|
||||
target: '_blank',
|
||||
},
|
||||
{ name: 'ZIM Manager', href: '/settings/zim', icon: FolderIcon, current: false },
|
||||
{
|
||||
name: 'Zim Remote Explorer',
|
||||
|
|
|
|||
|
|
@ -6,4 +6,9 @@ export type SystemInformationResponse = {
|
|||
mem: Systeminformation.MemData
|
||||
os: Systeminformation.OsData
|
||||
disk: Systeminformation.DiskLayoutData[]
|
||||
}
|
||||
|
||||
// Type inferrence is not working properly with usePage and shared props, so we define this type manually
|
||||
export type UsePageProps = {
|
||||
appVersion: string
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user