diff --git a/lib/menu.ts b/lib/menu.ts index e224392..16820e1 100644 --- a/lib/menu.ts +++ b/lib/menu.ts @@ -366,7 +366,7 @@ async function parseRemotes(remotes: string[]) { // console.log('[parseRemotes] Adding Stop Serve (' + currentServe.Addr.split(':')[0] + ') for ', remote) const stopServeMenuItem = await MenuItem.new({ id: `stop-serve-${currentServe.id}`, - text: 'Stop Serve', + text: 'Stop Serve (' + currentServe.id.split('-')[0]?.toUpperCase() + ')', action: async () => { try { await showLoadingTray() diff --git a/lib/rclone/constants.ts b/lib/rclone/constants.ts index ee91a90..53712e1 100644 --- a/lib/rclone/constants.ts +++ b/lib/rclone/constants.ts @@ -79,3 +79,5 @@ export const SUPPORTED_BACKENDS = [ 'yandex', 'zoho', ] + +export const SERVE_TYPES = ['dlna', 'ftp', 'sftp', 'http', 'nfs', 'restic', 's3', 'webdav'] as const diff --git a/src/components/RemoteCreateDrawer.tsx b/src/components/RemoteCreateDrawer.tsx index 52d6752..80e1581 100644 --- a/src/components/RemoteCreateDrawer.tsx +++ b/src/components/RemoteCreateDrawer.tsx @@ -103,6 +103,7 @@ export default function RemoteCreateDrawer({ // console.log(value) setConfig({ ...config, [option.Name]: value }) }} + allowsCustomValue={true} > {(item) => ( {(item) => ( +) { + const params = {} as Record + for (const [key, value] of Object.entries(input) as [string, FlagValue][]) { + const flag = dictionary.find((flag) => flag.FieldName === key) + if (flag) { + params[flag.Name] = value + } + } + return params +} export default function Serve() { - const remotes = useStore((state) => state.remotes) - const [remote, setRemote] = useState(undefined) + const [searchParams] = useSearchParams() + + const [source, setSource] = useState( + searchParams.get('initialSource') || undefined + ) const [type, setType] = useState<(typeof SERVE_TYPES)[number] | undefined>(undefined) const [isServing, setIsServing] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [jsonError, setJsonError] = useState<'mount' | 'vfs' | 'filter' | 'config' | null>(null) + const [jsonError, setJsonError] = useState<'serve' | 'vfs' | 'filter' | 'config' | null>(null) const [serveOptionsLocked, setServeOptionsLocked] = useState(false) const [serveOptions, setServeOptions] = useState>({}) const [serveOptionsJson, setServeOptionsJson] = useState('{}') const [vfsOptionsLocked, setVfsOptionsLocked] = useState(false) - const [_, setVfsOptions] = useState>({}) + const [vfsOptions, setVfsOptions] = useState>({}) const [vfsOptionsJson, setVfsOptionsJson] = useState('{}') + const [vfsFlags, setVfsFlags] = useState>>([]) const [filterOptionsLocked, setFilterOptionsLocked] = useState(false) const [filterOptions, setFilterOptions] = useState>({}) const [filterOptionsJson, setFilterOptionsJson] = useState('{}') - // const [configOptionsLocked, setConfigOptionsLocked] = useState(false) - // const [configOptions, setConfigOptions] = useState>({}) - // const [configOptionsJson, setConfigOptionsJson] = useState('{}') + const [configOptionsLocked, setConfigOptionsLocked] = useState(false) + const [configOptions, setConfigOptions] = useState>({}) + const [configOptionsJson, setConfigOptionsJson] = useState('{}') + + useEffect(() => { + console.log('serializeOptions', serializeOptions(vfsFlags, vfsOptions)) + }, [vfsFlags, vfsOptions]) const [currentGlobalOptions, setCurrentGlobalOptions] = useState([]) @@ -63,46 +88,99 @@ export default function Serve() { useEffect(() => { const storeData = usePersistedStore.getState() - if (!remote) return + const sourceRemoteName = getRemoteName(source) - const remoteConfig = storeData.remoteConfigList?.[remote] + let mergedFilterDefaults = {} + let mergedConfigDefaults = {} + let mergedVfsDefaults = {} - if (!vfsOptionsLocked) { - if (remoteConfig?.vfsDefaults && Object.keys(remoteConfig.vfsDefaults).length > 0) { - setVfsOptionsJson(JSON.stringify(remoteConfig.vfsDefaults, null, 2)) + // Helper function to merge defaults from a remote + const mergeRemoteDefaults = (remote: string | null) => { + if (!remote) return + + const remoteConfig = storeData.remoteConfigList?.[remote] || {} + + if (remoteConfig.vfsDefaults) { + mergedVfsDefaults = { + ...mergedVfsDefaults, + ...remoteConfig.vfsDefaults, + } + } + + if (remoteConfig.filterDefaults) { + mergedFilterDefaults = { + ...mergedFilterDefaults, + ...remoteConfig.filterDefaults, + } + } + + if (remoteConfig.configDefaults) { + mergedConfigDefaults = { + ...mergedConfigDefaults, + ...remoteConfig.configDefaults, + } } else { - setVfsOptionsJson(JSON.stringify(RCLONE_VFS_DEFAULTS, null, 2)) + mergedConfigDefaults = { + ...mergedConfigDefaults, + ...RCLONE_CONFIG_DEFAULTS, + } } } - if ( - remoteConfig?.filterDefaults && - Object.keys(remoteConfig.filterDefaults).length > 0 && - !filterOptionsLocked - ) { - setFilterOptionsJson(JSON.stringify(remoteConfig.filterDefaults, null, 2)) + // Only merge defaults for remote paths + if (sourceRemoteName) mergeRemoteDefaults(sourceRemoteName) + + if (Object.keys(mergedVfsDefaults).length > 0 && !vfsOptionsLocked) { + setVfsOptionsJson(JSON.stringify(mergedVfsDefaults, null, 2)) } - // if (!configOptionsLocked) { - // if ( - // storeData.remoteConfigList?.[remote]?.configDefaults && - // Object.keys(storeData.remoteConfigList?.[remote]?.configDefaults).length > 0 - // ) { - // setConfigOptionsJson( - // JSON.stringify(storeData.remoteConfigList[remote].configDefaults, null, 2) - // ) - // } else { - // setConfigOptionsJson(JSON.stringify(RCLONE_CONFIG_DEFAULTS, null, 2)) - // } - // } - }, [remote]) + if (Object.keys(mergedFilterDefaults).length > 0 && !filterOptionsLocked) { + setFilterOptionsJson(JSON.stringify(mergedFilterDefaults, null, 2)) + } + + if (Object.keys(mergedConfigDefaults).length > 0 && !configOptionsLocked) { + setConfigOptionsJson(JSON.stringify(mergedConfigDefaults, null, 2)) + } + }, [source]) + + // biome-ignore lint/correctness/useExhaustiveDependencies: when unlocking, we don't want to re-run the effect + useEffect(() => { + const storeData = usePersistedStore.getState() + + const sourceRemoteName = getRemoteName(source) + + let mergedServeDefaults = {} + + // Helper function to merge defaults from a remote + const mergeRemoteDefaults = (remote: string | null) => { + if (!remote) return + if (!type) return + + const remoteConfig = storeData.remoteConfigList?.[remote] || {} + + if (remoteConfig.serveDefaults?.[type]) { + mergedServeDefaults = { + ...mergedServeDefaults, + ...remoteConfig.serveDefaults?.[type], + } + } + } + + // Only merge defaults for remote paths + if (sourceRemoteName) mergeRemoteDefaults(sourceRemoteName) + + if (Object.keys(mergedServeDefaults).length > 0 && !serveOptionsLocked) { + setServeOptionsJson(JSON.stringify(mergedServeDefaults, null, 2)) + } + }, [source, type]) useEffect(() => { getCurrentGlobalFlags().then((flags) => setCurrentGlobalOptions(flags)) + getVfsFlags().then((flags) => setVfsFlags(flags)) }, []) useEffect(() => { - let step: 'mount' | 'vfs' | 'filter' | 'config' = 'mount' + let step: 'serve' | 'vfs' | 'filter' | 'config' = 'serve' try { setServeOptions(JSON.parse(serveOptionsJson)) @@ -112,27 +190,32 @@ export default function Serve() { step = 'filter' setFilterOptions(JSON.parse(filterOptionsJson)) - // step = 'config' - // setConfigOptions(JSON.parse(configOptionsJson)) + step = 'config' + setConfigOptions(JSON.parse(configOptionsJson)) setJsonError(null) } catch (error) { setJsonError(step) - console.error(`[Mount] Error parsing ${step} options:`, error) + console.error(`[Serve] Error parsing ${step} options:`, error) } - }, [serveOptionsJson, vfsOptionsJson, filterOptionsJson]) + }, [serveOptionsJson, vfsOptionsJson, filterOptionsJson, configOptionsJson]) async function handleStartServe() { - if (!remote || !type) return + if (!source || !type) return setIsLoading(true) try { + const vfsParams = serializeOptions(vfsFlags, vfsOptions) + await startServe({ type, - fs: `${remote}:/`, + // fs: `${remote}:/`, + fs: source, _filter: filterOptions as any, + _config: configOptions as any, ...serveOptions, + ...vfsParams, }) setIsServing(true) @@ -140,9 +223,9 @@ export default function Serve() { await triggerTrayRebuild() setIsLoading(false) } catch (err) { - console.error('[Mount] Failed to start mount:', err) + console.error('[Serve] Failed to start serve:', err) const errorMessage = - err instanceof Error ? err.message : 'Failed to start mount operation' + err instanceof Error ? err.message : 'Failed to start serve operation' await message(errorMessage, { title: 'Error', kind: 'error', @@ -154,26 +237,21 @@ export default function Serve() { const buttonText = (() => { if (isLoading) return 'STARTING...' if (isServing) return 'STARTED' - if (!remote) return 'Please select a remote' + if (!source) return 'Please select a source' if (!type) return 'Please select a serve type' - if (jsonError) return 'Invalid JSON for ' + jsonError.toUpperCase() + ' options' - if (!('addr' in serveOptions)) return 'Specify a listen address in the Serve options' return 'START SERVE' })() - console.log('serveOptions', serveOptions) - const buttonIcon = (() => { if (isLoading || isServing) return - if (!remote || !type) return + if (!source || !type) return if (jsonError) return if (!('addr' in serveOptions)) return return })() - console.log('type', type) return (
- +