2023-07-14 14:48:43 +02:00
2025-02-25 15:21:32 +01:00
import { APP _TYPES , PROXY _APP _ID , HSTATES , ISTATES , RSTATES } from '../constants.js' ;
2024-08-23 19:17:23 +02:00
import { fetcher } from 'pankow' ;
2023-07-14 14:48:43 +02:00
import { sleep } from 'pankow/utils' ;
2025-01-02 19:04:07 +01:00
import moment from 'moment' ;
function installationStateLabel ( app ) {
if ( ! app ) return '' ;
const waiting = app . progress === 0 ? ' (Queued)' : '' ;
switch ( app . installationState ) {
case ISTATES . PENDING _INSTALL :
return 'Installing' + waiting ;
case ISTATES . PENDING _CLONE :
return 'Cloning' + waiting ;
case ISTATES . PENDING _LOCATION _CHANGE :
case ISTATES . PENDING _CONFIGURE :
case ISTATES . PENDING _RECREATE _CONTAINER :
case ISTATES . PENDING _SERVICES _CHANGE :
case ISTATES . PENDING _DEBUG :
return 'Configuring' + waiting ;
case ISTATES . PENDING _RESIZE :
return 'Resizing' + waiting ;
case ISTATES . PENDING _DATA _DIR _MIGRATION :
return 'Migrating data' + waiting ;
case ISTATES . PENDING _UNINSTALL : return 'Uninstalling' + waiting ;
case ISTATES . PENDING _RESTORE : return 'Restoring' + waiting ;
case ISTATES . PENDING _IMPORT : return 'Importing' + waiting ;
case ISTATES . PENDING _UPDATE : return 'Updating' + waiting ;
case ISTATES . PENDING _BACKUP : return 'Backing up' + waiting ;
case ISTATES . PENDING _START : return 'Starting' + waiting ;
case ISTATES . PENDING _STOP : return 'Stopping' + waiting ;
case ISTATES . PENDING _RESTART : return 'Restarting' + waiting ;
case ISTATES . ERROR : {
if ( app . error && app . error . message === 'ETRYAGAIN' ) return 'DNS Error' ;
return 'Error' ;
}
case ISTATES . INSTALLED : {
if ( app . debugMode ) {
return 'Recovery Mode' ;
} else if ( app . runState === RSTATES . RUNNING ) {
if ( ! app . health ) return 'Starting...' ; // no data yet
if ( app . type === APP _TYPES . LINK ) return '' ;
if ( app . health === HSTATES . HEALTHY ) return 'Running' ;
return 'Not responding' ; // dead/exit/unhealthy
} else if ( app . runState === RSTATES . STOPPED ) {
return 'Stopped' ;
} else {
return app . runState ;
}
}
default : return app . installationState ;
}
}
function installationActive ( app ) {
if ( app . installationState === ISTATES . ERROR ) return false ;
if ( app . installationState === ISTATES . INSTALLED ) return false ;
return true ;
}
function appProgressMessage ( app ) {
return app . message || ( app . error ? app . error . message : '' ) ;
}
2025-01-31 21:02:48 +01:00
function create ( ) {
const accessToken = localStorage . token ;
const origin = import . meta . env . VITE _API _ORIGIN || window . location . origin ;
2025-01-02 19:04:07 +01:00
async function getTask ( appId ) {
let error , result ;
try {
result = await fetcher . get ( ` ${ origin } /api/v1/apps/ ${ appId } /task ` , { access _token : accessToken } ) ;
} catch ( e ) {
error = e ;
}
if ( error || result . status !== 200 ) {
console . error ( 'Failed to get task for app.' , error || result . status ) ;
return null ;
}
return result . body ;
}
2023-07-14 14:48:43 +02:00
return {
2024-12-29 00:36:48 +01:00
name : 'AppsModel' ,
2025-01-02 19:04:07 +01:00
getTask ,
2025-02-21 14:07:07 +01:00
isStopped ( app ) {
if ( app . installationState === ISTATES . PENDING _START || app . installationState === ISTATES . PENDING _STOP ) {
return app . installationState === ISTATES . PENDING _START ;
} else {
return app . runState === RSTATES . STOPPED ;
}
} ,
2025-01-06 14:35:14 +01:00
async install ( manifest , config ) {
const data = {
appStoreId : manifest . id + '@' + manifest . version ,
subdomain : config . subdomain ,
domain : config . domain ,
secondaryDomains : config . secondaryDomains ,
ports : config . ports ,
accessRestriction : config . accessRestriction ,
cert : config . cert ,
key : config . key ,
sso : config . sso ,
overwriteDns : config . overwriteDns ,
upstreamUri : config . upstreamUri ,
backupId : config . backupId // when restoring from archive
} ;
let error , result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps ` , data , { access _token : accessToken } ) ;
} catch ( e ) {
error = e ;
}
if ( error || result . status !== 202 ) {
console . error ( 'Failed to install app.' , error || result . status ) ;
return error || result . body ;
}
return '' ;
} ,
2024-12-29 00:36:48 +01:00
async list ( ) {
let error , result ;
try {
result = await fetcher . get ( ` ${ origin } /api/v1/apps ` , { access _token : accessToken } ) ;
} catch ( e ) {
error = e ;
}
2025-01-25 17:09:53 +01:00
if ( error || result . status !== 200 ) return [ error || result ] ;
2024-12-29 00:36:48 +01:00
2025-01-02 19:04:07 +01:00
for ( const app of result . body . apps ) {
app . ssoAuth = app . sso && ( app . manifest . addons [ 'ldap' ] || app . manifest . addons [ 'oidc' ] || app . manifest . addons [ 'proxyAuth' ] ) ; // checking app.sso first ensures app.manifest.addons is not null
2025-02-25 15:21:32 +01:00
app . type = app . manifest . id === PROXY _APP _ID ? APP _TYPES . PROXIED : APP _TYPES . APP ;
2025-01-02 19:04:07 +01:00
2025-01-05 16:40:04 +01:00
// only fetch if we have permissions and a taskId is set/active
if ( ! app . taskId || ( app . accessLevel !== 'operator' && app . accessLevel !== 'admin' ) ) {
2025-01-02 19:04:07 +01:00
app . progress = 0 ;
app . message = '' ;
app . taskMinutesActive = 0 ;
continue ;
}
const task = await getTask ( app . id ) ;
if ( task ) {
app . progress = task . percent ;
app . message = task . message ;
app . taskMinutesActive = moment . duration ( moment . utc ( ) . diff ( moment . utc ( task . creationTime ) ) ) . asMinutes ( ) ;
} else {
app . progress = 0 ;
app . message = '' ;
app . taskMinutesActive = 0 ;
}
}
2025-01-25 17:09:53 +01:00
return [ null , result . body . apps ] ;
2024-12-29 00:36:48 +01:00
} ,
2025-01-21 16:54:56 +01:00
async get ( id ) {
2023-07-14 14:48:43 +02:00
let error , result ;
try {
2024-08-23 19:17:23 +02:00
result = await fetcher . get ( ` ${ origin } /api/v1/apps/ ${ id } ` , { access _token : accessToken } ) ;
2023-07-14 14:48:43 +02:00
} catch ( e ) {
error = e ;
}
2025-01-21 16:54:56 +01:00
if ( error || result . status !== 200 ) return [ error || result ] ;
2025-02-25 15:21:32 +01:00
const app = result . body ;
app . ssoAuth = app . sso && ( app . manifest . addons [ 'ldap' ] || app . manifest . addons [ 'oidc' ] || app . manifest . addons [ 'proxyAuth' ] ) ; // checking app.sso first ensures app.manifest.addons is not null
app . type = app . manifest . id === PROXY _APP _ID ? APP _TYPES . PROXIED : APP _TYPES . APP ;
// only fetch if we have permissions and a taskId is set/active
if ( ! app . taskId || ( app . accessLevel !== 'operator' && app . accessLevel !== 'admin' ) ) {
app . progress = 0 ;
app . message = '' ;
app . taskMinutesActive = 0 ;
} else {
const task = await getTask ( app . id ) ;
if ( task ) {
app . progress = task . percent ;
app . message = task . message ;
app . taskMinutesActive = moment . duration ( moment . utc ( ) . diff ( moment . utc ( task . creationTime ) ) ) . asMinutes ( ) ;
} else {
app . progress = 0 ;
app . message = '' ;
app . taskMinutesActive = 0 ;
}
}
return [ null , app ] ;
2023-07-14 14:48:43 +02:00
} ,
2025-01-27 12:18:15 +01:00
async restart ( id ) {
2025-02-22 18:31:21 +01:00
let result ;
2023-07-14 14:48:43 +02:00
try {
2024-08-23 19:17:23 +02:00
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /restart ` , null , { access _token : accessToken } ) ;
2023-07-14 14:48:43 +02:00
} catch ( e ) {
2025-02-22 18:31:21 +01:00
return [ e ] ;
2023-07-14 14:48:43 +02:00
}
2025-02-22 18:31:21 +01:00
if ( result . status !== 202 ) return [ result ] ;
2023-07-14 14:48:43 +02:00
while ( true ) {
let result ;
try {
2024-08-23 19:17:23 +02:00
result = await fetcher . get ( ` ${ origin } /api/v1/apps/ ${ id } ` , { access _token : accessToken } ) ;
2023-07-14 14:48:43 +02:00
} catch ( e ) {
2025-02-22 18:31:21 +01:00
return [ e ] ;
2023-07-14 14:48:43 +02:00
}
2025-02-22 18:31:21 +01:00
if ( result . status !== 200 ) return [ result ] ;
2023-07-14 14:48:43 +02:00
2025-02-22 18:31:21 +01:00
// are we done here?
if ( result . body . installationState !== ISTATES . INSTALLED ) await sleep ( 2000 ) ;
else return [ null ] ;
2023-07-14 14:48:43 +02:00
}
2025-02-20 16:12:36 +01:00
} ,
2025-02-21 12:20:23 +01:00
async start ( id ) {
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /start ` , { } , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 202 ) return [ result ] ;
return [ null , result . body . taskId ] ;
} ,
async stop ( id ) {
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /stop ` , { } , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 202 ) return [ result ] ;
return [ null , result . body . taskId ] ;
} ,
2025-02-20 16:12:36 +01:00
async configure ( id , setting , data ) {
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /configure/ ${ setting } ` , data , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 200 && result . status !== 202 ) return [ result ] ;
return [ null ] ;
} ,
2025-02-21 14:07:07 +01:00
async uninstall ( id ) {
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /uninstall ` , { } , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 202 ) return [ result ] ;
return [ null ] ;
} ,
2025-02-21 16:30:59 +01:00
async getEvents ( id ) {
let result ;
try {
result = await fetcher . get ( ` ${ origin } /api/v1/apps/ ${ id } /eventlog ` , { page : 1 , per _page : 100 , access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 200 ) return [ result ] ;
return [ null , result . body . eventlogs ] ;
} ,
2025-02-21 20:58:43 +01:00
async checkForUpdates ( id ) {
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /check_for_updates ` , { } , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 200 ) return [ result ] ;
return [ null , result . body . update ] ;
} ,
async update ( id , manifest , skipBackup = false ) {
const data = {
appStoreId : ` ${ manifest . id } @ ${ manifest . version } ` ,
skipBackup : ! ! skipBackup ,
} ;
let result ;
try {
result = await fetcher . post ( ` ${ origin } /api/v1/apps/ ${ id } /update ` , data , { access _token : accessToken } ) ;
} catch ( e ) {
return [ e ] ;
}
if ( result . status !== 202 ) return [ result ] ;
return [ null ] ;
} ,
2023-07-14 14:48:43 +02:00
} ;
}
export default {
2025-01-02 19:04:07 +01:00
create ,
installationStateLabel ,
installationActive ,
appProgressMessage ,
2023-07-14 14:48:43 +02:00
} ;