feat: move log into monitoring extension (#2662)
This commit is contained in:
parent
3be0b3af65
commit
3f23de6c28
@ -1,9 +1,12 @@
|
||||
import { basename, isAbsolute, join, relative } from 'path'
|
||||
|
||||
import { Processor } from './Processor'
|
||||
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
||||
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
||||
import { appResourcePath } from '../../helper/path'
|
||||
import {
|
||||
log as writeLog,
|
||||
appResourcePath,
|
||||
getAppConfigurations as appConfiguration,
|
||||
updateAppConfiguration,
|
||||
} from '../../helper'
|
||||
|
||||
export class App implements Processor {
|
||||
observer?: Function
|
||||
@ -56,13 +59,6 @@ export class App implements Processor {
|
||||
writeLog(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message to log file.
|
||||
*/
|
||||
logServer(args: any) {
|
||||
writeServerLog(args)
|
||||
}
|
||||
|
||||
getAppConfigurations() {
|
||||
return appConfiguration()
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
||||
import { logServer } from '../../../helper/log'
|
||||
import {
|
||||
getJanDataFolderPath,
|
||||
getJanExtensionsPath,
|
||||
getSystemResourceInfo,
|
||||
log,
|
||||
} from '../../../helper'
|
||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||
import {
|
||||
@ -69,7 +73,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
}),
|
||||
}
|
||||
|
||||
logServer(`[NITRO]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
||||
log(`[SERVER]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
||||
|
||||
// Convert settings.prompt_template to system_prompt, user_prompt, ai_prompt
|
||||
if (modelMetadata.settings.prompt_template) {
|
||||
@ -140,7 +144,7 @@ const runNitroAndLoadModel = async (modelId: string, modelSettings: NitroModelSe
|
||||
}
|
||||
|
||||
const spawnNitroProcess = async (): Promise<void> => {
|
||||
logServer(`[NITRO]::Debug: Spawning Nitro subprocess...`)
|
||||
log(`[SERVER]::Debug: Spawning Nitro subprocess...`)
|
||||
|
||||
let binaryFolder = join(
|
||||
getJanExtensionsPath(),
|
||||
@ -155,8 +159,8 @@ const spawnNitroProcess = async (): Promise<void> => {
|
||||
|
||||
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
||||
// Execute the binary
|
||||
logServer(
|
||||
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||
log(
|
||||
`[SERVER]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||
)
|
||||
subprocess = spawn(
|
||||
executableOptions.executablePath,
|
||||
@ -172,20 +176,20 @@ const spawnNitroProcess = async (): Promise<void> => {
|
||||
|
||||
// Handle subprocess output
|
||||
subprocess.stdout.on('data', (data: any) => {
|
||||
logServer(`[NITRO]::Debug: ${data}`)
|
||||
log(`[SERVER]::Debug: ${data}`)
|
||||
})
|
||||
|
||||
subprocess.stderr.on('data', (data: any) => {
|
||||
logServer(`[NITRO]::Error: ${data}`)
|
||||
log(`[SERVER]::Error: ${data}`)
|
||||
})
|
||||
|
||||
subprocess.on('close', (code: any) => {
|
||||
logServer(`[NITRO]::Debug: Nitro exited with code: ${code}`)
|
||||
log(`[SERVER]::Debug: Nitro exited with code: ${code}`)
|
||||
subprocess = undefined
|
||||
})
|
||||
|
||||
tcpPortUsed.waitUntilUsed(NITRO_DEFAULT_PORT, 300, 30000).then(() => {
|
||||
logServer(`[NITRO]::Debug: Nitro is ready`)
|
||||
log(`[SERVER]::Debug: Nitro is ready`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -267,7 +271,7 @@ const validateModelStatus = async (): Promise<void> => {
|
||||
retries: 5,
|
||||
retryDelay: 500,
|
||||
}).then(async (res: Response) => {
|
||||
logServer(`[NITRO]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
||||
log(`[SERVER]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
||||
// If the response is OK, check model_loaded status.
|
||||
if (res.ok) {
|
||||
const body = await res.json()
|
||||
@ -282,7 +286,7 @@ const validateModelStatus = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> => {
|
||||
logServer(`[NITRO]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
||||
log(`[SERVER]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
||||
const fetchRT = require('fetch-retry')
|
||||
const fetchRetry = fetchRT(fetch)
|
||||
|
||||
@ -296,11 +300,11 @@ const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> =>
|
||||
retryDelay: 500,
|
||||
})
|
||||
.then((res: any) => {
|
||||
logServer(`[NITRO]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
||||
log(`[SERVER]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
||||
return Promise.resolve(res)
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logServer(`[NITRO]::Error: Load model failed with error ${err}`)
|
||||
log(`[SERVER]::Error: Load model failed with error ${err}`)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
@ -323,7 +327,7 @@ export const stopModel = async (_modelId: string) => {
|
||||
})
|
||||
}, 5000)
|
||||
const tcpPortUsed = require('tcp-port-used')
|
||||
logServer(`[NITRO]::Debug: Request to kill Nitro`)
|
||||
log(`[SERVER]::Debug: Request to kill Nitro`)
|
||||
|
||||
fetch(NITRO_HTTP_KILL_URL, {
|
||||
method: 'DELETE',
|
||||
@ -337,7 +341,7 @@ export const stopModel = async (_modelId: string) => {
|
||||
// don't need to do anything, we still kill the subprocess
|
||||
})
|
||||
.then(() => tcpPortUsed.waitUntilFree(NITRO_DEFAULT_PORT, 300, 5000))
|
||||
.then(() => logServer(`[NITRO]::Debug: Nitro process is terminated`))
|
||||
.then(() => log(`[SERVER]::Debug: Nitro process is terminated`))
|
||||
.then(() =>
|
||||
resolve({
|
||||
message: 'Model stopped',
|
||||
|
||||
@ -150,31 +150,3 @@ export const getEngineConfiguration = async (engineId: string) => {
|
||||
full_url: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get server log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getServerLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations()
|
||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||
}
|
||||
return join(logFolderPath, 'server.log')
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get app log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getAppLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations()
|
||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||
}
|
||||
return join(logFolderPath, 'app.log')
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from './config'
|
||||
export * from './download'
|
||||
export * from './log'
|
||||
export * from './logger'
|
||||
export * from './module'
|
||||
export * from './path'
|
||||
export * from './resource'
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import util from 'util'
|
||||
import { getAppLogPath, getServerLogPath } from './config'
|
||||
|
||||
export const log = (message: string) => {
|
||||
const path = getAppLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[APP]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
writeLog(message, path)
|
||||
}
|
||||
|
||||
export const logServer = (message: string) => {
|
||||
const path = getServerLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[SERVER]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
writeLog(message, path)
|
||||
}
|
||||
|
||||
const writeLog = (message: string, logPath: string) => {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.writeFileSync(logPath, message)
|
||||
} else {
|
||||
const logFile = fs.createWriteStream(logPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
logFile.write(util.format(message) + '\n')
|
||||
logFile.close()
|
||||
console.debug(message)
|
||||
}
|
||||
}
|
||||
81
core/src/node/helper/logger.ts
Normal file
81
core/src/node/helper/logger.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// Abstract Logger class that all loggers should extend.
|
||||
export abstract class Logger {
|
||||
// Each logger must have a unique name.
|
||||
abstract name: string
|
||||
|
||||
/**
|
||||
* Log message to log file.
|
||||
* This method should be overridden by subclasses to provide specific logging behavior.
|
||||
*/
|
||||
abstract log(args: any): void
|
||||
}
|
||||
|
||||
// LoggerManager is a singleton class that manages all registered loggers.
|
||||
export class LoggerManager {
|
||||
// Map of registered loggers, keyed by their names.
|
||||
public loggers = new Map<string, Logger>()
|
||||
|
||||
// Array to store logs that are queued before the loggers are registered.
|
||||
queuedLogs: any[] = []
|
||||
|
||||
// Flag to indicate whether flushLogs is currently running.
|
||||
private isFlushing = false
|
||||
|
||||
// Register a new logger. If a logger with the same name already exists, it will be replaced.
|
||||
register(logger: Logger) {
|
||||
this.loggers.set(logger.name, logger)
|
||||
}
|
||||
// Unregister a logger by its name.
|
||||
unregister(name: string) {
|
||||
this.loggers.delete(name)
|
||||
}
|
||||
|
||||
get(name: string) {
|
||||
return this.loggers.get(name)
|
||||
}
|
||||
|
||||
// Flush queued logs to all registered loggers.
|
||||
flushLogs() {
|
||||
// If flushLogs is already running, do nothing.
|
||||
if (this.isFlushing) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isFlushing = true
|
||||
|
||||
while (this.queuedLogs.length > 0 && this.loggers.size > 0) {
|
||||
const log = this.queuedLogs.shift()
|
||||
this.loggers.forEach((logger) => {
|
||||
logger.log(log)
|
||||
})
|
||||
}
|
||||
|
||||
this.isFlushing = false
|
||||
}
|
||||
|
||||
// Log message using all registered loggers.
|
||||
log(args: any) {
|
||||
this.queuedLogs.push(args)
|
||||
|
||||
this.flushLogs()
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of the logger.
|
||||
* If an instance doesn't exist, it creates a new one.
|
||||
* This ensures that there is only one LoggerManager instance at any time.
|
||||
*/
|
||||
static instance(): LoggerManager {
|
||||
let instance: LoggerManager | undefined = global.core?.logger
|
||||
if (!instance) {
|
||||
instance = new LoggerManager()
|
||||
if (!global.core) global.core = {}
|
||||
global.core.logger = instance
|
||||
}
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
export const log = (...args: any) => {
|
||||
LoggerManager.instance().log(args)
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
import { SystemResourceInfo } from '../../types'
|
||||
import { physicalCpuCount } from './config'
|
||||
import { log } from './log'
|
||||
import { log } from './logger'
|
||||
|
||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||
const cpu = await physicalCpuCount()
|
||||
const message = `[NITRO]::CPU informations - ${cpu}`
|
||||
log(message)
|
||||
log(`[NITRO]::CPU informations - ${cpu}`)
|
||||
|
||||
return {
|
||||
numCpuPhysicalCore: cpu,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { GpuSetting, OperatingSystemInfo } from '../miscellaneous'
|
||||
|
||||
/**
|
||||
* Monitoring extension for system monitoring.
|
||||
* @extends BaseExtension
|
||||
@ -14,4 +16,14 @@ export interface MonitoringInterface {
|
||||
* @returns {Promise<any>} A promise that resolves with the current system load.
|
||||
*/
|
||||
getCurrentLoad(): Promise<any>
|
||||
|
||||
/**
|
||||
* Returns the GPU configuration.
|
||||
*/
|
||||
getGpuSetting(): Promise<GpuSetting>
|
||||
|
||||
/**
|
||||
* Returns information about the operating system.
|
||||
*/
|
||||
getOsInfo(): Promise<OperatingSystemInfo>
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ import { cleanUpAndQuit } from './utils/clean'
|
||||
import { setupExtensions } from './utils/extension'
|
||||
import { setupCore } from './utils/setup'
|
||||
import { setupReactDevTool } from './utils/dev'
|
||||
import { cleanLogs } from './utils/log'
|
||||
|
||||
import { trayManager } from './managers/tray'
|
||||
import { logSystemInfo } from './utils/system'
|
||||
@ -75,7 +74,6 @@ app
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(() => cleanLogs())
|
||||
|
||||
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
||||
windowManager.showMainWindow()
|
||||
@ -111,7 +109,6 @@ function createMainWindow() {
|
||||
windowManager.createMainWindow(preloadPath, startUrl)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles various IPC messages from the renderer process.
|
||||
*/
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
export function cleanLogs(
|
||||
maxFileSizeBytes?: number | undefined,
|
||||
daysToKeep?: number | undefined,
|
||||
delayMs?: number | undefined
|
||||
): void {
|
||||
const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB
|
||||
const days = daysToKeep ?? 7 // 7 days
|
||||
const delays = delayMs ?? 10000 // 10 seconds
|
||||
const logDirectory = path.join(getJanDataFolderPath(), 'logs')
|
||||
|
||||
// Perform log cleaning
|
||||
const currentDate = new Date()
|
||||
fs.readdir(logDirectory, (err, files) => {
|
||||
if (err) {
|
||||
console.error('Error reading log directory:', err)
|
||||
return
|
||||
}
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(logDirectory, file)
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('Error getting file stats:', err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check size
|
||||
if (stats.size > size) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error deleting log file:', err)
|
||||
return
|
||||
}
|
||||
console.debug(
|
||||
`Deleted log file due to exceeding size limit: ${filePath}`
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Check age
|
||||
const creationDate = new Date(stats.ctime)
|
||||
const daysDifference = Math.floor(
|
||||
(currentDate.getTime() - creationDate.getTime()) /
|
||||
(1000 * 3600 * 24)
|
||||
)
|
||||
if (daysDifference > days) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error deleting log file:', err)
|
||||
return
|
||||
}
|
||||
console.debug(`Deleted old log file: ${filePath}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Schedule the next execution with doubled delays
|
||||
setTimeout(() => {
|
||||
cleanLogs(maxFileSizeBytes, daysToKeep, delays * 2)
|
||||
}, delays)
|
||||
}
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan Assistant plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# Create a Jan Plugin using Typescript
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
## Create Your Own Extension
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -14,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -39,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan inference plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan inference plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from '@janhq/core'
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, 'run', 0)
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan inference plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan Model Management plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# Jan Monitoring plugin
|
||||
# Create a Jan Extension using Typescript
|
||||
|
||||
Created using Jan app example
|
||||
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||
|
||||
# Create a Jan Plugin using Typescript
|
||||
## Create Your Own Extension
|
||||
|
||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
||||
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||
|
||||
1. Click the Use this template button at the top of the repository
|
||||
2. Select Create a new repository
|
||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
||||
|
||||
1. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a tgz file in your plugin directory now
|
||||
There will be a tgz file in your extension directory now
|
||||
|
||||
## Update the Plugin Metadata
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
||||
plugin name, main entry, description and version.
|
||||
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||
extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
||||
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||
|
||||
## Update the Plugin Code
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||
source code that will be run when your extension functions are invoked. You can replace the
|
||||
contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your plugin code:
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||
this.inference(data)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Plugin Core module, see the
|
||||
For more information about the Jan Extension Core module, see the
|
||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
||||
|
||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||
|
||||
21
extensions/monitoring-extension/resources/settings.json
Normal file
21
extensions/monitoring-extension/resources/settings.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"key": "log-enabled",
|
||||
"title": "App Logging Enabled",
|
||||
"description": "We recommend enabling this setting to help us improve the app. Your data will be kept private on your computer, and you can opt out at any time.",
|
||||
"controllerType": "checkbox",
|
||||
"controllerProps": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "log-cleaning-interval",
|
||||
"title": "Log Cleaning Interval",
|
||||
"description": "Log cleaning interval in milliseconds.",
|
||||
"controllerType": "input",
|
||||
"controllerProps": {
|
||||
"value": "120000",
|
||||
"placeholder": "Interval in milliseconds. E.g. 120000"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -4,6 +4,7 @@ import sourceMaps from 'rollup-plugin-sourcemaps'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
import json from '@rollup/plugin-json'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
const settingJson = require('./resources/settings.json')
|
||||
const packageJson = require('./package.json')
|
||||
|
||||
export default [
|
||||
@ -19,6 +20,7 @@ export default [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||
SETTINGS: JSON.stringify(settingJson),
|
||||
}),
|
||||
// Allow json resolution
|
||||
json(),
|
||||
|
||||
@ -1,27 +1,63 @@
|
||||
import {
|
||||
GpuSetting,
|
||||
MonitoringExtension,
|
||||
MonitoringInterface,
|
||||
OperatingSystemInfo,
|
||||
executeOnMain,
|
||||
} from '@janhq/core'
|
||||
|
||||
declare const SETTINGS: Array<any>
|
||||
|
||||
enum Settings {
|
||||
logEnabled = 'log-enabled',
|
||||
logCleaningInterval = 'log-cleaning-interval',
|
||||
}
|
||||
/**
|
||||
* JanMonitoringExtension is a extension that provides system monitoring functionality.
|
||||
* It implements the MonitoringExtension interface from the @janhq/core package.
|
||||
*/
|
||||
export default class JanMonitoringExtension extends MonitoringExtension {
|
||||
export default class JanMonitoringExtension
|
||||
extends MonitoringExtension
|
||||
implements MonitoringInterface
|
||||
{
|
||||
/**
|
||||
* Called when the extension is loaded.
|
||||
*/
|
||||
async onLoad() {
|
||||
// Register extension settings
|
||||
this.registerSettings(SETTINGS)
|
||||
|
||||
const logEnabled = await this.getSetting<boolean>(Settings.logEnabled, true)
|
||||
const logCleaningInterval = parseInt(
|
||||
await this.getSetting<string>(Settings.logCleaningInterval, '120000')
|
||||
)
|
||||
// Register File Logger provided by this extension
|
||||
await executeOnMain(NODE, 'registerLogger', {
|
||||
logEnabled,
|
||||
logCleaningInterval: isNaN(logCleaningInterval)
|
||||
? 120000
|
||||
: logCleaningInterval,
|
||||
})
|
||||
|
||||
// Attempt to fetch nvidia info
|
||||
await executeOnMain(NODE, 'updateNvidiaInfo')
|
||||
}
|
||||
|
||||
onSettingUpdate<T>(key: string, value: T): void {
|
||||
if (key === Settings.logEnabled) {
|
||||
executeOnMain(NODE, 'updateLogger', { logEnabled: value })
|
||||
} else if (key === Settings.logCleaningInterval) {
|
||||
executeOnMain(NODE, 'updateLogger', { logCleaningInterval: value })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the extension is unloaded.
|
||||
*/
|
||||
onUnload(): void {}
|
||||
onUnload(): void {
|
||||
// Register File Logger provided by this extension
|
||||
executeOnMain(NODE, 'unregisterLogger')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GPU configuration.
|
||||
@ -47,6 +83,10 @@ export default class JanMonitoringExtension extends MonitoringExtension {
|
||||
return executeOnMain(NODE, 'getCurrentLoad')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the OS
|
||||
* @returns
|
||||
*/
|
||||
getOsInfo(): Promise<OperatingSystemInfo> {
|
||||
return executeOnMain(NODE, 'getOsInfo')
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
GpuSetting,
|
||||
GpuSettingInfo,
|
||||
LoggerManager,
|
||||
OperatingSystemInfo,
|
||||
ResourceInfo,
|
||||
SupportedPlatforms,
|
||||
@ -12,6 +13,7 @@ import { exec } from 'child_process'
|
||||
import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { FileLogger } from './logger'
|
||||
|
||||
/**
|
||||
* Path to the settings directory
|
||||
@ -346,3 +348,22 @@ export const getOsInfo = (): OperatingSystemInfo => {
|
||||
|
||||
return osInfo
|
||||
}
|
||||
|
||||
export const registerLogger = ({ logEnabled, logCleaningInterval }) => {
|
||||
const logger = new FileLogger(logEnabled, logCleaningInterval)
|
||||
LoggerManager.instance().register(logger)
|
||||
logger.cleanLogs()
|
||||
}
|
||||
|
||||
export const unregisterLogger = () => {
|
||||
LoggerManager.instance().unregister('file')
|
||||
}
|
||||
|
||||
export const updateLogger = ({ logEnabled, logCleaningInterval }) => {
|
||||
const logger = LoggerManager.instance().loggers.get('file') as FileLogger
|
||||
if (logger && logEnabled !== undefined) logger.logEnabled = logEnabled
|
||||
if (logger && logCleaningInterval)
|
||||
logger.logCleaningInterval = logCleaningInterval
|
||||
// Rerun
|
||||
logger && logger.cleanLogs()
|
||||
}
|
||||
|
||||
138
extensions/monitoring-extension/src/node/logger.ts
Normal file
138
extensions/monitoring-extension/src/node/logger.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import fs from 'fs'
|
||||
import util from 'util'
|
||||
import {
|
||||
getAppConfigurations,
|
||||
getJanDataFolderPath,
|
||||
Logger,
|
||||
} from '@janhq/core/node'
|
||||
import path, { join } from 'path'
|
||||
|
||||
export class FileLogger extends Logger {
|
||||
name = 'file'
|
||||
logCleaningInterval: number = 120000
|
||||
timeout: NodeJS.Timeout | null = null
|
||||
appLogPath: string = './'
|
||||
logEnabled: boolean = true
|
||||
|
||||
constructor(
|
||||
logEnabled: boolean = true,
|
||||
logCleaningInterval: number = 120000
|
||||
) {
|
||||
super()
|
||||
this.logEnabled = logEnabled
|
||||
if (logCleaningInterval) this.logCleaningInterval = logCleaningInterval
|
||||
|
||||
const appConfigurations = getAppConfigurations()
|
||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||
}
|
||||
|
||||
this.appLogPath = join(logFolderPath, 'app.log')
|
||||
}
|
||||
|
||||
log(args: any) {
|
||||
if (!this.logEnabled) return
|
||||
let message = args[0]
|
||||
const scope = args[1]
|
||||
if (!message) return
|
||||
const path = this.appLogPath
|
||||
if (!scope && !message.startsWith('[')) {
|
||||
message = `[APP]::${message}`
|
||||
} else if (scope) {
|
||||
message = `${scope}::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
writeLog(message, path)
|
||||
}
|
||||
|
||||
cleanLogs(
|
||||
maxFileSizeBytes?: number | undefined,
|
||||
daysToKeep?: number | undefined
|
||||
): void {
|
||||
// clear existing timeout
|
||||
// incase we rerun it with different values
|
||||
if (this.timeout) clearTimeout(this.timeout)
|
||||
this.timeout = undefined
|
||||
|
||||
if (!this.logEnabled) return
|
||||
|
||||
console.log(
|
||||
'Validating app logs. Next attempt in ',
|
||||
this.logCleaningInterval
|
||||
)
|
||||
|
||||
const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB
|
||||
const days = daysToKeep ?? 7 // 7 days
|
||||
const logDirectory = path.join(getJanDataFolderPath(), 'logs')
|
||||
|
||||
// Perform log cleaning
|
||||
const currentDate = new Date()
|
||||
fs.readdir(logDirectory, (err, files) => {
|
||||
if (err) {
|
||||
console.error('Error reading log directory:', err)
|
||||
return
|
||||
}
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(logDirectory, file)
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('Error getting file stats:', err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check size
|
||||
if (stats.size > size) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error deleting log file:', err)
|
||||
return
|
||||
}
|
||||
console.debug(
|
||||
`Deleted log file due to exceeding size limit: ${filePath}`
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Check age
|
||||
const creationDate = new Date(stats.ctime)
|
||||
const daysDifference = Math.floor(
|
||||
(currentDate.getTime() - creationDate.getTime()) /
|
||||
(1000 * 3600 * 24)
|
||||
)
|
||||
if (daysDifference > days) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error deleting log file:', err)
|
||||
return
|
||||
}
|
||||
console.debug(`Deleted old log file: ${filePath}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Schedule the next execution with doubled delays
|
||||
this.timeout = setTimeout(
|
||||
() => this.cleanLogs(maxFileSizeBytes, daysToKeep),
|
||||
this.logCleaningInterval
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const writeLog = (message: string, logPath: string) => {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.writeFileSync(logPath, message)
|
||||
} else {
|
||||
const logFile = fs.createWriteStream(logPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
logFile.write(util.format(message) + '\n')
|
||||
logFile.close()
|
||||
console.debug(message)
|
||||
}
|
||||
}
|
||||
35
server/helpers/logger.ts
Normal file
35
server/helpers/logger.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { log } from '@janhq/core/node'
|
||||
import { FastifyBaseLogger } from 'fastify'
|
||||
import { ChildLoggerOptions } from 'fastify/types/logger'
|
||||
import pino from 'pino'
|
||||
|
||||
export class Logger implements FastifyBaseLogger {
|
||||
child(
|
||||
bindings: pino.Bindings,
|
||||
options?: ChildLoggerOptions | undefined
|
||||
): FastifyBaseLogger {
|
||||
return new Logger()
|
||||
}
|
||||
level = 'info'
|
||||
|
||||
silent = () => {}
|
||||
|
||||
info = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
error = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
debug = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
fatal = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
warn = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
trace = function (msg: any) {
|
||||
log(msg)
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,9 @@
|
||||
import fastify from 'fastify'
|
||||
import dotenv from 'dotenv'
|
||||
import {
|
||||
getServerLogPath,
|
||||
v1Router,
|
||||
logServer,
|
||||
getJanExtensionsPath,
|
||||
} from '@janhq/core/node'
|
||||
import { v1Router, log, getJanExtensionsPath } from '@janhq/core/node'
|
||||
import { join } from 'path'
|
||||
import tcpPortUsed from 'tcp-port-used'
|
||||
import { Logger } from './helpers/logger'
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config()
|
||||
@ -52,7 +48,7 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
||||
const inUse = await tcpPortUsed.check(Number(configs.port), configs.host)
|
||||
if (inUse) {
|
||||
const errorMessage = `Port ${configs.port} is already in use.`
|
||||
logServer(errorMessage)
|
||||
log(errorMessage, '[SERVER]')
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
}
|
||||
@ -62,19 +58,15 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
||||
hostSetting = configs?.host ?? JAN_API_HOST
|
||||
portSetting = configs?.port ?? JAN_API_PORT
|
||||
corsEnabled = configs?.isCorsEnabled ?? true
|
||||
const serverLogPath = getServerLogPath()
|
||||
|
||||
// Start the server
|
||||
try {
|
||||
// Log server start
|
||||
if (isVerbose) logServer(`Debug: Starting JAN API server...`)
|
||||
if (isVerbose) log(`Debug: Starting JAN API server...`, '[SERVER]')
|
||||
|
||||
// Initialize Fastify server with logging
|
||||
server = fastify({
|
||||
logger: {
|
||||
level: 'info',
|
||||
file: serverLogPath,
|
||||
},
|
||||
logger: new Logger(),
|
||||
})
|
||||
|
||||
// Register CORS if enabled
|
||||
@ -134,14 +126,15 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
||||
.then(() => {
|
||||
// Log server listening
|
||||
if (isVerbose)
|
||||
logServer(
|
||||
`Debug: JAN API listening at: http://${hostSetting}:${portSetting}`
|
||||
log(
|
||||
`Debug: JAN API listening at: http://${hostSetting}:${portSetting}`,
|
||||
'[SERVER]'
|
||||
)
|
||||
})
|
||||
return true
|
||||
} catch (e) {
|
||||
// Log any errors
|
||||
if (isVerbose) logServer(`Error: ${e}`)
|
||||
if (isVerbose) log(`Error: ${e}`, '[SERVER]')
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -152,11 +145,11 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
||||
export const stopServer = async () => {
|
||||
try {
|
||||
// Log server stop
|
||||
if (isVerbose) logServer(`Debug: Server stopped`)
|
||||
if (isVerbose) log(`Debug: Server stopped`, '[SERVER]')
|
||||
// Stop the server
|
||||
await server?.close()
|
||||
} catch (e) {
|
||||
// Log any errors
|
||||
if (isVerbose) logServer(`Error: ${e}`)
|
||||
if (isVerbose) log(`Error: ${e}`, '[SERVER]')
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,9 +28,11 @@ const ServerLogs = (props: ServerLogsProps) => {
|
||||
|
||||
const updateLogs = useCallback(
|
||||
() =>
|
||||
getLogs('server').then((log) => {
|
||||
getLogs('app').then((log) => {
|
||||
if (typeof log?.split === 'function') {
|
||||
setLogs(log.split(/\r?\n|\r|\n/g))
|
||||
setLogs(
|
||||
log.split(/\r?\n|\r|\n/g).filter((e) => e.includes('[SERVER]::'))
|
||||
)
|
||||
}
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -19,7 +19,8 @@ export class ExtensionManager {
|
||||
* @param extension - The extension to register.
|
||||
*/
|
||||
register<T extends BaseExtension>(name: string, extension: T) {
|
||||
this.extensions.set(extension.type() ?? name, extension)
|
||||
// Register for naming use
|
||||
this.extensions.set(name, extension)
|
||||
|
||||
// Register AI Engines
|
||||
if ('provider' in extension && typeof extension.provider === 'string') {
|
||||
@ -35,10 +36,26 @@ export class ExtensionManager {
|
||||
* @param type - The type of the extension to retrieve.
|
||||
* @returns The extension, if found.
|
||||
*/
|
||||
get<T extends BaseExtension>(
|
||||
type: ExtensionTypeEnum | string
|
||||
): T | undefined {
|
||||
return this.extensions.get(type) as T | undefined
|
||||
get<T extends BaseExtension>(type: ExtensionTypeEnum): T | undefined {
|
||||
return this.getAll().findLast((e) => e.type() === type) as T | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a extension by its type.
|
||||
* @param type - The type of the extension to retrieve.
|
||||
* @returns The extension, if found.
|
||||
*/
|
||||
getByName(name: string): BaseExtension | undefined {
|
||||
return this.extensions.get(name) as BaseExtension | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a extension by its type.
|
||||
* @param type - The type of the extension to retrieve.
|
||||
* @returns The extension, if found.
|
||||
*/
|
||||
getAll(): BaseExtension[] {
|
||||
return Array.from(this.extensions.values())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -25,12 +25,12 @@ export const useLogs = () => {
|
||||
)
|
||||
|
||||
const openServerLog = useCallback(async () => {
|
||||
const fullPath = await joinPath([janDataFolderPath, 'logs', 'server.log'])
|
||||
const fullPath = await joinPath([janDataFolderPath, 'logs', 'app.log'])
|
||||
return openFileExplorer(fullPath)
|
||||
}, [janDataFolderPath])
|
||||
|
||||
const clearServerLog = useCallback(async () => {
|
||||
await fs.writeFileSync(await joinPath(['file://logs', 'server.log']), '')
|
||||
await fs.writeFileSync(await joinPath(['file://logs', 'app.log']), '')
|
||||
}, [])
|
||||
|
||||
return { getLogs, openServerLog, clearServerLog }
|
||||
|
||||
@ -79,7 +79,7 @@ const TensorRtExtensionItem: React.FC<Props> = ({ item }) => {
|
||||
|
||||
useEffect(() => {
|
||||
const getExtensionInstallationState = async () => {
|
||||
const extension = extensionManager.get(item.name ?? '')
|
||||
const extension = extensionManager.getByName(item.name ?? '')
|
||||
if (!extension) return
|
||||
|
||||
if (typeof extension?.installationState === 'function') {
|
||||
@ -92,13 +92,13 @@ const TensorRtExtensionItem: React.FC<Props> = ({ item }) => {
|
||||
}, [item.name, isInstalling])
|
||||
|
||||
useEffect(() => {
|
||||
const extension = extensionManager.get(item.name ?? '')
|
||||
const extension = extensionManager.getByName(item.name ?? '')
|
||||
if (!extension) return
|
||||
setCompatibility(extension.compatibility())
|
||||
}, [setCompatibility, item.name])
|
||||
|
||||
const onInstallClick = useCallback(async () => {
|
||||
const extension = extensionManager.get(item.name ?? '')
|
||||
const extension = extensionManager.getByName(item.name ?? '')
|
||||
if (!extension) return
|
||||
|
||||
await extension.install()
|
||||
|
||||
@ -17,13 +17,12 @@ const ExtensionSetting: React.FC = () => {
|
||||
const getExtensionSettings = async () => {
|
||||
if (!selectedExtensionName) return
|
||||
const allSettings: SettingComponentProps[] = []
|
||||
const baseExtension = extensionManager.get(selectedExtensionName)
|
||||
const baseExtension = extensionManager.getByName(selectedExtensionName)
|
||||
if (!baseExtension) return
|
||||
if (typeof baseExtension.getSettings === 'function') {
|
||||
const setting = await baseExtension.getSettings()
|
||||
if (setting) allSettings.push(...setting)
|
||||
}
|
||||
|
||||
setSettings(allSettings)
|
||||
}
|
||||
getExtensionSettings()
|
||||
@ -40,7 +39,7 @@ const ExtensionSetting: React.FC = () => {
|
||||
|
||||
const extensionName = setting.extensionName
|
||||
if (extensionName) {
|
||||
extensionManager.get(extensionName)?.updateSettings([setting])
|
||||
extensionManager.getByName(extensionName)?.updateSettings([setting])
|
||||
}
|
||||
|
||||
return setting
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { CheckboxComponentProps, SettingComponentProps } from '@janhq/core'
|
||||
import { Switch } from '@janhq/uikit'
|
||||
import { Marked, Renderer } from 'marked'
|
||||
|
||||
type Props = {
|
||||
settingProps: SettingComponentProps
|
||||
onValueChanged?: (e: boolean) => void
|
||||
}
|
||||
|
||||
const marked: Marked = new Marked({
|
||||
renderer: {
|
||||
link: (href, title, text) => {
|
||||
return Renderer.prototype.link
|
||||
?.apply(this, [href, title, text])
|
||||
.replace('<a', "<a class='text-blue-500' target='_blank'")
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const SettingDetailToggleItem: React.FC<Props> = ({
|
||||
settingProps,
|
||||
onValueChanged,
|
||||
}) => {
|
||||
const { value } = settingProps.controllerProps as CheckboxComponentProps
|
||||
|
||||
const description = marked.parse(settingProps.description ?? '', {
|
||||
async: false,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex w-full justify-between py-6">
|
||||
<div className="flex flex-1 flex-col space-y-1">
|
||||
<h1 className="text-base font-bold">{settingProps.title}</h1>
|
||||
{
|
||||
<div
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
className="text-sm font-normal text-muted-foreground"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<Switch checked={value} onCheckedChange={onValueChanged} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingDetailToggleItem
|
||||
@ -1,6 +1,7 @@
|
||||
import { SettingComponentProps } from '@janhq/core'
|
||||
|
||||
import SettingDetailTextInputItem from './SettingDetailTextInputItem'
|
||||
import SettingDetailToggleItem from './SettingDetailToggleItem'
|
||||
|
||||
type Props = {
|
||||
componentProps: SettingComponentProps[]
|
||||
@ -23,6 +24,16 @@ const SettingDetailItem: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
return (
|
||||
<SettingDetailToggleItem
|
||||
key={data.key}
|
||||
settingProps={data}
|
||||
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@ -15,20 +15,13 @@ const SettingMenu: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const getAllSettings = async () => {
|
||||
const activeExtensions = await extensionManager.getActive()
|
||||
const extensionsMenu: string[] = []
|
||||
|
||||
for (const extension of activeExtensions) {
|
||||
const extensionName = extension.name
|
||||
if (!extensionName) continue
|
||||
|
||||
const baseExtension = extensionManager.get(extensionName)
|
||||
if (!baseExtension) continue
|
||||
|
||||
if (typeof baseExtension.getSettings === 'function') {
|
||||
const settings = await baseExtension.getSettings()
|
||||
const extensions = extensionManager.getAll()
|
||||
for (const extension of extensions) {
|
||||
if (typeof extension.getSettings === 'function') {
|
||||
const settings = await extension.getSettings()
|
||||
if (settings && settings.length > 0) {
|
||||
extensionsMenu.push(extensionName)
|
||||
extensionsMenu.push(extension.name ?? extension.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user