mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
feat: subscribe to release notes
This commit is contained in:
parent
c8de767052
commit
8cfe490b57
|
|
@ -1,7 +1,7 @@
|
|||
import { DockerService } from '#services/docker_service';
|
||||
import { SystemService } from '#services/system_service'
|
||||
import { SystemUpdateService } from '#services/system_update_service'
|
||||
import { affectServiceValidator, installServiceValidator } from '#validators/system';
|
||||
import { affectServiceValidator, installServiceValidator, subscribeToReleaseNotesValidator } from '#validators/system';
|
||||
import { inject } from '@adonisjs/core'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
|
||||
|
|
@ -102,4 +102,10 @@ export default class SystemController {
|
|||
const logs = this.systemUpdateService.getUpdateLogs();
|
||||
response.send({ logs });
|
||||
}
|
||||
|
||||
|
||||
async subscribeToReleaseNotes({ request }: HttpContext) {
|
||||
const reqData = await request.validateUsing(subscribeToReleaseNotesValidator);
|
||||
return await this.systemService.subscribeToReleaseNotes(reqData.email);
|
||||
}
|
||||
}
|
||||
|
|
@ -226,6 +226,34 @@ export class SystemService {
|
|||
}
|
||||
}
|
||||
|
||||
async subscribeToReleaseNotes(email: string): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'https://api.projectnomad.us/api/v1/lists/release-notes/subscribe',
|
||||
{ email },
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Successfully subscribed to release notes',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to subscribe: ${response.statusText}`,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error subscribing to release notes:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to subscribe: ${error instanceof Error ? error.message : error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current state of Docker containers against the database records and updates the database accordingly.
|
||||
* It will mark services as not installed if their corresponding containers do not exist, regardless of their running state.
|
||||
|
|
@ -241,7 +269,7 @@ export class SystemService {
|
|||
const containerExists = serviceStatusList.find(
|
||||
(s) => s.service_name === service.service_name
|
||||
)
|
||||
|
||||
|
||||
if (service.installed) {
|
||||
// If marked as installed but container doesn't exist, mark as not installed
|
||||
if (!containerExists) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
import vine from '@vinejs/vine'
|
||||
|
||||
export const installServiceValidator = vine.compile(vine.object({
|
||||
service_name: vine.string().trim()
|
||||
}));
|
||||
|
||||
export const affectServiceValidator = vine.compile(vine.object({
|
||||
export const installServiceValidator = vine.compile(
|
||||
vine.object({
|
||||
service_name: vine.string().trim(),
|
||||
action: vine.enum(['start', 'stop', 'restart'])
|
||||
}));
|
||||
})
|
||||
)
|
||||
|
||||
export const affectServiceValidator = vine.compile(
|
||||
vine.object({
|
||||
service_name: vine.string().trim(),
|
||||
action: vine.enum(['start', 'stop', 'restart']),
|
||||
})
|
||||
)
|
||||
|
||||
export const subscribeToReleaseNotesValidator = vine.compile(
|
||||
vine.object({
|
||||
email: vine.string().email().trim(),
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|||
className?: string;
|
||||
labelClassName?: string;
|
||||
inputClassName?: string;
|
||||
containerClassName?: string;
|
||||
leftIcon?: React.ReactNode;
|
||||
error?: boolean;
|
||||
required?: boolean;
|
||||
|
|
@ -18,6 +19,7 @@ const Input: React.FC<InputProps> = ({
|
|||
name,
|
||||
labelClassName,
|
||||
inputClassName,
|
||||
containerClassName,
|
||||
leftIcon,
|
||||
error,
|
||||
required,
|
||||
|
|
@ -31,7 +33,7 @@ const Input: React.FC<InputProps> = ({
|
|||
>
|
||||
{label}{required ? "*" : ""}
|
||||
</label>
|
||||
<div className="mt-1.5">
|
||||
<div className={classNames("mt-1.5", containerClassName)}>
|
||||
<div className="relative">
|
||||
{leftIcon && (
|
||||
<div className="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||
|
|
|
|||
|
|
@ -285,6 +285,16 @@ class API {
|
|||
return response.data
|
||||
})()
|
||||
}
|
||||
|
||||
async subscribeToReleaseNotes(email: string) {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.post<{ success: boolean; message: string }>(
|
||||
'/system/subscribe-release-notes',
|
||||
{ email }
|
||||
)
|
||||
return response.data
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
export default new API()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import { useEffect, useState } from 'react'
|
|||
import { IconCircleCheck } from '@tabler/icons-react'
|
||||
import { SystemUpdateStatus } from '../../../types/system'
|
||||
import api from '~/lib/api'
|
||||
import Input from '~/components/inputs/Input'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useNotifications } from '~/context/NotificationContext'
|
||||
|
||||
export default function SystemUpdatePage(props: {
|
||||
system: {
|
||||
|
|
@ -18,11 +21,14 @@ export default function SystemUpdatePage(props: {
|
|||
currentVersion: string
|
||||
}
|
||||
}) {
|
||||
const { addNotification } = useNotifications()
|
||||
|
||||
const [isUpdating, setIsUpdating] = useState(false)
|
||||
const [updateStatus, setUpdateStatus] = useState<SystemUpdateStatus | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showLogs, setShowLogs] = useState(false)
|
||||
const [logs, setLogs] = useState<string>('')
|
||||
const [email, setEmail] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUpdating) return
|
||||
|
|
@ -116,10 +122,33 @@ export default function SystemUpdatePage(props: {
|
|||
if (updateStatus?.stage === 'error')
|
||||
return <IconAlertCircle className="h-12 w-12 text-desert-red" />
|
||||
if (isUpdating) return <IconRefresh className="h-12 w-12 text-desert-green animate-spin" />
|
||||
if (props.system.updateAvailable) return <IconArrowBigUpLines className="h-16 w-16 text-desert-green" />
|
||||
if (props.system.updateAvailable)
|
||||
return <IconArrowBigUpLines className="h-16 w-16 text-desert-green" />
|
||||
return <IconCircleCheck className="h-16 w-16 text-desert-olive" />
|
||||
}
|
||||
|
||||
const subscribeToReleaseNotesMutation = useMutation({
|
||||
mutationKey: ['subscribeToReleaseNotes'],
|
||||
mutationFn: (email: string) => api.subscribeToReleaseNotes(email),
|
||||
onSuccess: (data) => {
|
||||
if (data && data.success) {
|
||||
addNotification({ type: 'success', message: 'Successfully subscribed to release notes!' })
|
||||
setEmail('')
|
||||
} else {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: `Failed to subscribe: ${data?.message || 'Unknown error'}`,
|
||||
})
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: `Error subscribing to release notes: ${error.message || 'Unknown error'}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<Head title="System Update" />
|
||||
|
|
@ -128,7 +157,8 @@ export default function SystemUpdatePage(props: {
|
|||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-desert-green mb-2">System Update</h1>
|
||||
<p className="text-desert-stone-dark">
|
||||
Keep your Project N.O.M.A.D. instance up to date with the latest features and improvements.
|
||||
Keep your Project N.O.M.A.D. instance up to date with the latest features and
|
||||
improvements.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -161,9 +191,7 @@ export default function SystemUpdatePage(props: {
|
|||
{!isUpdating && (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-2">
|
||||
{props.system.updateAvailable
|
||||
? 'Update Available'
|
||||
: 'System Up to Date'}
|
||||
{props.system.updateAvailable ? 'Update Available' : 'System Up to Date'}
|
||||
</h2>
|
||||
<p className="text-desert-stone-dark mb-6">
|
||||
{props.system.updateAvailable
|
||||
|
|
@ -305,6 +333,43 @@ export default function SystemUpdatePage(props: {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border shadow-md overflow-hidden py-6 mt-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center p-8 gap-y-8 md:gap-y-0 gap-x-8">
|
||||
<div>
|
||||
<h2 className="max-w-xl text-lg font-bold text-desert-green sm:text-xl lg:col-span-7">
|
||||
Want to stay updated with the latest from Project N.O.M.A.D.? Subscribe to receive
|
||||
release notes directly to your inbox. Unsubscribe anytime.
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex gap-x-3">
|
||||
<Input
|
||||
name="email"
|
||||
label=""
|
||||
type="email"
|
||||
placeholder="Your email address"
|
||||
disabled={false}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full"
|
||||
containerClassName="!mt-0"
|
||||
/>
|
||||
<StyledButton
|
||||
variant="primary"
|
||||
disabled={!email}
|
||||
onClick={() => subscribeToReleaseNotesMutation.mutateAsync(email)}
|
||||
loading={subscribeToReleaseNotesMutation.isPending}
|
||||
>
|
||||
Subscribe
|
||||
</StyledButton>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-desert-stone-dark">
|
||||
We care about your privacy. Project N.O.M.A.D. will never share your email with
|
||||
third parties or send you spam.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Alert
|
||||
type="info"
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ router
|
|||
router.post('/services/affect', [SystemController, 'affectService'])
|
||||
router.post('/services/install', [SystemController, 'installService'])
|
||||
router.post('/services/force-reinstall', [SystemController, 'forceReinstallService'])
|
||||
router.post('/subscribe-release-notes', [SystemController, 'subscribeToReleaseNotes'])
|
||||
router.get('/latest-version', [SystemController, 'checkLatestVersion'])
|
||||
router.post('/update', [SystemController, 'requestSystemUpdate'])
|
||||
router.get('/update/status', [SystemController, 'getSystemUpdateStatus'])
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user