diff --git a/admin/app/exceptions/internal_server_error_exception.ts b/admin/app/exceptions/internal_server_error_exception.ts new file mode 100644 index 0000000..d49acec --- /dev/null +++ b/admin/app/exceptions/internal_server_error_exception.ts @@ -0,0 +1,6 @@ +import { Exception } from '@adonisjs/core/exceptions' + +export default class InternalServerErrorException extends Exception { + static status = 500 + static code = 'E_INTERNAL_SERVER_ERROR' +} \ No newline at end of file diff --git a/admin/app/services/docs_service.ts b/admin/app/services/docs_service.ts index 2c27e75..f4af334 100644 --- a/admin/app/services/docs_service.ts +++ b/admin/app/services/docs_service.ts @@ -2,6 +2,7 @@ import Markdoc from '@markdoc/markdoc' import { streamToString } from '../../util/docs.js' import { getFile, getFileStatsIfExists, listDirectoryContentsRecursive } from '../utils/fs.js' import path from 'path' +import InternalServerErrorException from '#exceptions/internal_server_error_exception' export class DocsService { private docsPath = path.join(process.cwd(), 'docs') @@ -23,35 +24,47 @@ export class DocsService { } parse(content: string) { - const ast = Markdoc.parse(content) - const config = this.getConfig() - const errors = Markdoc.validate(ast, config) + try { + const ast = Markdoc.parse(content) + const config = this.getConfig() + const errors = Markdoc.validate(ast, config) - if (errors.length > 0) { - throw new Error(`Markdoc validation errors: ${errors.map((e) => e.error).join(', ')}`) + // Filter out attribute-undefined errors which may be caused by emojis and special characters + const criticalErrors = errors.filter((e) => e.error.id !== 'attribute-undefined') + if (criticalErrors.length > 0) { + console.error('Markdoc validation errors:', errors.map((e) => JSON.stringify(e.error)).join(', ')) + throw new Error('Markdoc validation failed') + } + + return Markdoc.transform(ast, config) + } catch (error) { + console.log('Error parsing Markdoc content:', error) + throw new InternalServerErrorException(`Error parsing content: ${(error as Error).message}`) } - - return Markdoc.transform(ast, config) } async parseFile(_filename: string) { - if (!_filename) { - throw new Error('Filename is required') - } + try { + if (!_filename) { + throw new Error('Filename is required') + } - const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md` + const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md` - const fileExists = await getFileStatsIfExists(path.join(this.docsPath, filename)) - if (!fileExists) { - throw new Error(`File not found: ${filename}`) - } + const fileExists = await getFileStatsIfExists(path.join(this.docsPath, filename)) + if (!fileExists) { + throw new Error(`File not found: ${filename}`) + } - const fileStream = await getFile(path.join(this.docsPath, filename), 'stream') - if (!fileStream) { - throw new Error(`Failed to read file stream: ${filename}`) + const fileStream = await getFile(path.join(this.docsPath, filename), 'stream') + if (!fileStream) { + throw new Error(`Failed to read file stream: ${filename}`) + } + const content = await streamToString(fileStream) + return this.parse(content) + } catch (error) { + throw new InternalServerErrorException(`Error parsing file: ${(error as Error).message}`) } - const content = await streamToString(fileStream) - return this.parse(content) } private prettify(filename: string) { @@ -87,6 +100,21 @@ export class DocsService { id: { type: String }, }, }, + list: { + render: 'List', + attributes: { + ordered: { type: Boolean }, + start: { type: Number }, + }, + }, + list_item: { + render: 'ListItem', + attributes: { + marker: { type: String }, + className: { type: String }, + class: { type: String } + } + }, }, } } diff --git a/admin/docs/about.md b/admin/docs/about.md new file mode 100644 index 0000000..6558ca7 --- /dev/null +++ b/admin/docs/about.md @@ -0,0 +1,5 @@ +# About Project N.O.M.A.D. + +Project N.O.M.A.D. (Node for Offline Media, Archives, and Data; "Nomad" for short) is a project started in 2025 by Chris Sherwood of [Crosstalk Solutions, LLC.](https://crosstalksolutions.com). The goal of the project is not to create just another utility for storing offline resources, but rather to allow users to run their own ultime "survival computer". + +While many similar offline survival computers are designed to be run on bare-minimum, lightweight hardware, Project N.O.M.A.D. is quite the opposite. To install and run the available AI tools, we highly encourage the use of a beefy, GPU-backed device to make the most of your install. \ No newline at end of file diff --git a/admin/docs/release-notes.md b/admin/docs/release-notes.md index beb1361..96b9870 100644 --- a/admin/docs/release-notes.md +++ b/admin/docs/release-notes.md @@ -1,6 +1,148 @@ # Release Notes -## Version 1.1.0 - July 20, 2025 +## Version 1.10.1 - December 5, 2025 + +### ✨ Improvements +1. This is a test +- **Kiwix**: ZIM storage path + +--- + +## Version 1.10.0 - December 5, 2025 + +### 🚀 Features + +- Disk info monitoring + +### ✨ Improvements + +- **Install**: Add Redis env variables to compose file +- **Kiwix**: initial download and setup + +--- + +## Version 1.9.0 - December 5, 2025 + +### 🚀 Features + +- Background job management with BullMQ + +### ✨ Improvements + +- **Install**: Character escaping in env variables +- **Install**: Host env variable + +--- + +## Version 1.8.0 - December 5, 2025 + +### 🚀 Features + +- Alert and button styles redesign +- System info page redesign +- **Collections**: Curated ZIM Collections +- **Collections**: add Preppers Library +- **Collections**: add slug, icon, and language +- **Collections**: store additional data with resources list +- Custom map and ZIM file downloads (WIP) +- New maps system (WIP) + +### ✨ Improvements + +- **DockerService**: cleanup old OSM stuff +- **Install**: standardize compose file names +- Hide query devtools in prod + +--- + +## Version 1.7.0 - December 5, 2025 + +### 🚀 Features + +- Alert and button styles redesign +- System info page redesign +- **Collections**: Curated ZIM Collections +- **Collections**: add Preppers Library +- **Collections**: add slug, icon, and language +- **Collections**: store additional data with resources list +- Custom map and ZIM file downloads (WIP) +- New maps system (WIP) + +### ✨ Improvements + +- **DockerService**: cleanup old OSM stuff +- **Install**: standardize compose file names +- Hide query devtools in prod + +--- + +## Version 1.6.0 - November 18, 2025 + +### 🚀 Features + +- Added Kolibri to standard app library + +### ✨ Improvements + +- Standardize container names in management-compose + +--- + +## Version 1.5.0 - November 18, 2025 + +### 🚀 Features + +- Version footer and fix CI version handling + +--- + +## Version 1.4.0 - November 18, 2025 + +### 🚀 Features + +- **Services**: Friendly names and descriptions + +### ✨ Improvements + +- **Scripts**: logs directory creation improvements +- **Scripts**: fix type in management-compose file path + +--- + +## Version 1.3.0 - October 9, 2025 + +### 🚀 New Features + +- Uninstall script now removes non-management Nomad app containers + +### ✨ Improvements + +- **OpenStreetMap**: Apply dir permission fixes more robustly + +--- + +## Version 1.2.0 - October 7, 2025 + +### 🚀 New Features + +- Added CyberChef to standard app library +- Added Dozzle to core containers for enhanced logs and metrics +- Added FlatNotes to standard app library +- Uninstall helper script available + +### ✨ Improvements + +- **OpenStreetMap**: + - Fixed directory paths and access issues + - Improved error handling + - Fixed renderer file permissions + - Fixed absolute host path issue +- **ZIM Manager**: + - Initial ZIM download now hosted in Project Nomad GitHub repo for better availability + +--- + +## Version 1.1.0 - August 20, 2025 ### 🚀 New Features diff --git a/admin/inertia/components/MarkdocRenderer.tsx b/admin/inertia/components/MarkdocRenderer.tsx index 7549b07..db1d543 100644 --- a/admin/inertia/components/MarkdocRenderer.tsx +++ b/admin/inertia/components/MarkdocRenderer.tsx @@ -1,5 +1,8 @@ -import React, { JSX } from 'react' +import React from 'react' import Markdoc from '@markdoc/markdoc' +import { Heading } from './markdoc/Heading' +import { List } from './markdoc/List' +import { ListItem } from './markdoc/ListItem' // Custom components for Markdoc tags const Callout = ({ @@ -27,37 +30,12 @@ const Callout = ({ ) } -const Heading = ({ - level, - id, - children, -}: { - level: number - id: string - children: React.ReactNode -}) => { - const Tag = `h${level}` as keyof JSX.IntrinsicElements - const sizes = { - 1: 'text-3xl font-bold', - 2: 'text-2xl font-semibold', - 3: 'text-xl font-semibold', - 4: 'text-lg font-semibold', - 5: 'text-base font-semibold', - 6: 'text-sm font-semibold', - } - - return ( - // @ts-ignore - - {children} - - ) -} - // Component mapping for Markdoc const components = { Callout, Heading, + List, + ListItem, } interface MarkdocRendererProps { @@ -65,11 +43,9 @@ interface MarkdocRendererProps { } const MarkdocRenderer: React.FC = ({ content }) => { - return ( -
- {Markdoc.renderers.react(content, React, { components })} -
- ) + console.log('Markdoc content:', content) + + return
{Markdoc.renderers.react(content, React, { components })}
} export default MarkdocRenderer diff --git a/admin/inertia/components/markdoc/Heading.tsx b/admin/inertia/components/markdoc/Heading.tsx new file mode 100644 index 0000000..c9cd1e7 --- /dev/null +++ b/admin/inertia/components/markdoc/Heading.tsx @@ -0,0 +1,28 @@ +import React, { JSX } from 'react' + +export function Heading({ + level, + id, + children, +}: { + level: number + id: string + children: React.ReactNode +}) { + const Component = `h${level}` as keyof JSX.IntrinsicElements + const sizes = { + 1: 'text-3xl font-bold', + 2: 'text-2xl font-semibold', + 3: 'text-xl font-semibold', + 4: 'text-lg font-semibold', + 5: 'text-base font-semibold', + 6: 'text-sm font-semibold', + } + + return ( + // @ts-ignore + + {children} + + ) +} diff --git a/admin/inertia/components/markdoc/List.tsx b/admin/inertia/components/markdoc/List.tsx new file mode 100644 index 0000000..f60fc68 --- /dev/null +++ b/admin/inertia/components/markdoc/List.tsx @@ -0,0 +1,20 @@ +export function List({ + ordered = false, + start, + children, +}: { + ordered?: boolean + start?: number + children: React.ReactNode +}) { + const className = ordered + ? 'list-decimal list-outside !ml-12 mb-4 space-y-1' + : 'list-disc list-outside !ml-12 mb-4 space-y-1' + const Tag = ordered ? 'ol' : 'ul' + return ( + // @ts-ignore + + {children} + + ) +} diff --git a/admin/inertia/components/markdoc/ListItem.tsx b/admin/inertia/components/markdoc/ListItem.tsx new file mode 100644 index 0000000..2dcf57f --- /dev/null +++ b/admin/inertia/components/markdoc/ListItem.tsx @@ -0,0 +1,4 @@ + +export function ListItem({ children }: { children: React.ReactNode }) { + return
  • {children}
  • +} \ No newline at end of file diff --git a/admin/inertia/tsconfig.json b/admin/inertia/tsconfig.json index c04c853..9a332e6 100644 --- a/admin/inertia/tsconfig.json +++ b/admin/inertia/tsconfig.json @@ -8,5 +8,5 @@ "~/*": ["./*"], }, }, - "include": ["./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.ts", "./**/*.tsx", "components/markdoc/nodes/heading.markdoc.js"], } \ No newline at end of file