import { Head } from '@inertiajs/react'
import StyledTable from '~/components/StyledTable'
import SettingsLayout from '~/layouts/SettingsLayout'
import { ServiceSlim } from '../../../types/services'
import { getServiceLink } from '~/lib/navigation'
import StyledButton from '~/components/StyledButton'
import { useModals } from '~/context/ModalContext'
import StyledModal from '~/components/StyledModal'
import api from '~/lib/api'
import { useEffect, useState } from 'react'
import InstallActivityFeed from '~/components/InstallActivityFeed'
import LoadingSpinner from '~/components/LoadingSpinner'
import useErrorNotification from '~/hooks/useErrorNotification'
import useInternetStatus from '~/hooks/useInternetStatus'
import useServiceInstallationActivity from '~/hooks/useServiceInstallationActivity'
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
import { IconCheck } from '@tabler/icons-react'
export default function SettingsPage(props: { system: { services: ServiceSlim[] } }) {
const { openModal, closeAllModals } = useModals()
const { showError } = useErrorNotification()
const { isOnline } = useInternetStatus()
const installActivity = useServiceInstallationActivity()
const [isInstalling, setIsInstalling] = useState(false)
const [loading, setLoading] = useState(false)
useEffect(() => {
if (installActivity.length === 0) return
if (installActivity.some((activity) => activity.type === 'completed')) {
// If any activity is completed, we can clear the installActivity state
setTimeout(() => {
window.location.reload() // Reload the page to reflect changes
}, 3000) // Clear after 3 seconds
}
}, [installActivity])
const handleInstallService = (service: ServiceSlim) => {
openModal(
{
installService(service.service_name)
closeAllModals()
}}
onCancel={closeAllModals}
open={true}
confirmText="Install"
cancelText="Cancel"
confirmVariant="primary"
icon={}
>
Are you sure you want to install {service.friendly_name || service.service_name}? This
will start the service and make it available in your Project N.O.M.A.D. instance. It may
take some time to complete.
,
'install-service-modal'
)
}
async function installService(serviceName: string) {
try {
if (!isOnline) {
showError('You must have an internet connection to install services.')
return
}
setIsInstalling(true)
const response = await api.installService(serviceName)
if (!response) {
throw new Error('An internal error occurred while trying to install the service.')
}
if (!response.success) {
throw new Error(response.message)
}
} catch (error) {
console.error('Error installing service:', error)
showError(`Failed to install service: ${error.message || 'Unknown error'}`)
} finally {
setIsInstalling(false)
}
}
async function handleAffectAction(record: ServiceSlim, action: 'start' | 'stop' | 'restart') {
try {
setLoading(true)
const response = await api.affectService(record.service_name, action)
if (!response) {
throw new Error('An internal error occurred while trying to affect the service.')
}
if (!response.success) {
throw new Error(response.message)
}
closeAllModals()
setTimeout(() => {
setLoading(false)
window.location.reload() // Reload the page to reflect changes
}, 3000) // Add small delay to allow for the action to complete
} catch (error) {
console.error(`Error affecting service ${record.service_name}:`, error)
showError(`Failed to ${action} service: ${error.message || 'Unknown error'}`)
}
}
async function handleForceReinstall(record: ServiceSlim) {
try {
setLoading(true)
const response = await api.forceReinstallService(record.service_name)
if (!response) {
throw new Error('An internal error occurred while trying to force reinstall the service.')
}
if (!response.success) {
throw new Error(response.message)
}
closeAllModals()
setTimeout(() => {
setLoading(false)
window.location.reload() // Reload the page to reflect changes
}, 3000) // Add small delay to allow for the action to complete
} catch (error) {
console.error(`Error force reinstalling service ${record.service_name}:`, error)
showError(`Failed to force reinstall service: ${error.message || 'Unknown error'}`)
}
}
const AppActions = ({ record }: { record: ServiceSlim }) => {
const ForceReinstallButton = () => (
{
openModal(
handleForceReinstall(record)}
onCancel={closeAllModals}
open={true}
confirmText={'Force Reinstall'}
cancelText="Cancel"
>
Are you sure you want to force reinstall {record.service_name}? This will{' '}
WIPE ALL DATA for this service and cannot be undone. You should
only do this if the service is malfunctioning and other troubleshooting steps have
failed.
,
`${record.service_name}-force-reinstall-modal`
)
}}
disabled={isInstalling}
>
Force Reinstall
)
if (!record) return null
if (!record.installed) {
return (
handleInstallService(record)}
disabled={isInstalling || !isOnline}
loading={isInstalling}
>
Install
)
}
return (
{
window.open(getServiceLink(record.ui_location || 'unknown'), '_blank')
}}
>
Open
{record.status && record.status !== 'unknown' && (
<>
{
openModal(
handleAffectAction(record, record.status === 'running' ? 'stop' : 'start')
}
onCancel={closeAllModals}
open={true}
confirmText={record.status === 'running' ? 'Stop' : 'Start'}
cancelText="Cancel"
>
Are you sure you want to {record.status === 'running' ? 'stop' : 'start'}{' '}
{record.service_name}?
,
`${record.service_name}-affect-modal`
)
}}
disabled={isInstalling}
>
{record.status === 'running' ? 'Stop' : 'Start'}
{record.status === 'running' && (
{
openModal(
handleAffectAction(record, 'restart')}
onCancel={closeAllModals}
open={true}
confirmText={'Restart'}
cancelText="Cancel"
>
Are you sure you want to restart {record.service_name}?
,
`${record.service_name}-affect-modal`
)
}}
disabled={isInstalling}
>
Restart
)}
>
)}
)
}
return (
Apps
Manage the applications that are available in your Project N.O.M.A.D. instance.
{loading && }
{!loading && (
className="font-semibold"
rowLines={true}
columns={[
{
accessor: 'friendly_name',
title: 'Name',
render(record) {
return (
{record.friendly_name || record.service_name}
{record.description}
)
},
},
{
accessor: 'ui_location',
title: 'Port',
render: (record) => (
{record.ui_location}
),
},
{
accessor: 'installed',
title: 'Installed',
render: (record) =>
record.installed ? : '',
},
{
accessor: 'actions',
title: 'Actions',
render: (record) => ,
},
]}
data={props.system.services}
/>
)}
{installActivity.length > 0 && (
)}
)
}