mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
## Summary Implements server-side remote configuration to decouple runtime behavior from build artifacts, enabling dynamic configuration updates without redeployment. ## Technical Changes - **Replaced** build-time constants (`__MIXPANEL_TOKEN__`, `__BUILD_FLAGS__`) with runtime configuration loaded from `/api/features` - Configuration now sourced from `window.__CONFIG__` (hydrated from `/api/features` endpoint) - **Added** `src/platform/remoteConfig/` service that polls server configuration every 30 seconds - **Modified** application bootstrap sequence in `main.ts` to load remote config before module initialization (required for cloud builds) - **Removed** global constants: `__BUILD_FLAGS__`, `__MIXPANEL_TOKEN__`. Runtime subscription enforcement toggle via `subscription_required` flag - Server health alerts with variant-based severity rendering (info/warning/error) via topbar badges ## Rationale - **Build-once-deploy-anywhere**: Single immutable artifact promoted through environments (staging → production) - **Zero-downtime configuration**: Update behavior without rebuilding or redeploying the application - **Incident response**: Disable features or display alerts dynamically in response to outages or degraded service - **Instant rollback**: Revert configuration changes server-side without artifact redeployment - **Progressive delivery**: Enable A/B testing, canary releases, and user/region-based configuration - **Environment parity**: Eliminate configuration drift between staging and production builds - Decouples deployment cadence from configuration changes - Enables GitOps workflows for configuration management separate from code deployments - Supports real-time operational control of client behavior - Reduces build matrix complexity (no per-environment builds) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6246-change-cloud-feature-flags-to-be-loaded-dynamically-at-runtime-rather-than-set-in-build-2966d73d3650811cbb41c9093961037a) by [Unito](https://www.unito.io)
231 lines
6.0 KiB
TypeScript
231 lines
6.0 KiB
TypeScript
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
|
|
import type { Component } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
|
import { isCloud } from '@/platform/distribution/types'
|
|
import type { SettingTreeNode } from '@/platform/settings/settingStore'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import type { SettingParams } from '@/platform/settings/types'
|
|
import { isElectron } from '@/utils/envUtil'
|
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
|
import { buildTree } from '@/utils/treeUtil'
|
|
|
|
interface SettingPanelItem {
|
|
node: SettingTreeNode
|
|
component: Component
|
|
}
|
|
|
|
export function useSettingUI(
|
|
defaultPanel?:
|
|
| 'about'
|
|
| 'keybinding'
|
|
| 'extension'
|
|
| 'server-config'
|
|
| 'user'
|
|
| 'credits'
|
|
| 'subscription'
|
|
) {
|
|
const { t } = useI18n()
|
|
const { isLoggedIn } = useCurrentUser()
|
|
const settingStore = useSettingStore()
|
|
const activeCategory = ref<SettingTreeNode | null>(null)
|
|
|
|
const settingRoot = computed<SettingTreeNode>(() => {
|
|
const root = buildTree(
|
|
Object.values(settingStore.settingsById).filter(
|
|
(setting: SettingParams) => setting.type !== 'hidden'
|
|
),
|
|
(setting: SettingParams) => setting.category || setting.id.split('.')
|
|
)
|
|
|
|
const floatingSettings = (root.children ?? []).filter((node) => node.leaf)
|
|
if (floatingSettings.length) {
|
|
root.children = (root.children ?? []).filter((node) => !node.leaf)
|
|
root.children.push({
|
|
key: 'Other',
|
|
label: 'Other',
|
|
leaf: false,
|
|
children: floatingSettings
|
|
})
|
|
}
|
|
|
|
return root
|
|
})
|
|
|
|
const settingCategories = computed<SettingTreeNode[]>(
|
|
() => settingRoot.value.children ?? []
|
|
)
|
|
|
|
// Define panel items
|
|
const aboutPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'about',
|
|
label: 'About',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/components/dialog/content/setting/AboutPanel.vue')
|
|
)
|
|
}
|
|
|
|
const creditsPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'credits',
|
|
label: 'Credits',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/components/dialog/content/setting/CreditsPanel.vue')
|
|
)
|
|
}
|
|
|
|
const subscriptionPanel: SettingPanelItem | null =
|
|
!isCloud || !window.__CONFIG__?.subscription_required
|
|
? null
|
|
: {
|
|
node: {
|
|
key: 'subscription',
|
|
label: 'PlanCredits',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() =>
|
|
import(
|
|
'@/platform/cloud/subscription/components/SubscriptionPanel.vue'
|
|
)
|
|
)
|
|
}
|
|
|
|
const userPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'user',
|
|
label: 'User',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/components/dialog/content/setting/UserPanel.vue')
|
|
)
|
|
}
|
|
|
|
const keybindingPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'keybinding',
|
|
label: 'Keybinding',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/components/dialog/content/setting/KeybindingPanel.vue')
|
|
)
|
|
}
|
|
|
|
const extensionPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'extension',
|
|
label: 'Extension',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/platform/settings/components/ExtensionPanel.vue')
|
|
)
|
|
}
|
|
|
|
const serverConfigPanel: SettingPanelItem = {
|
|
node: {
|
|
key: 'server-config',
|
|
label: 'Server-Config',
|
|
children: []
|
|
},
|
|
component: defineAsyncComponent(
|
|
() => import('@/platform/settings/components/ServerConfigPanel.vue')
|
|
)
|
|
}
|
|
|
|
const panels = computed<SettingPanelItem[]>(() =>
|
|
[
|
|
aboutPanel,
|
|
creditsPanel,
|
|
userPanel,
|
|
keybindingPanel,
|
|
extensionPanel,
|
|
...(isElectron() ? [serverConfigPanel] : []),
|
|
...(isCloud &&
|
|
window.__CONFIG__?.subscription_required &&
|
|
subscriptionPanel
|
|
? [subscriptionPanel]
|
|
: [])
|
|
].filter((panel) => panel.component)
|
|
)
|
|
|
|
/**
|
|
* The default category to show when the dialog is opened.
|
|
*/
|
|
const defaultCategory = computed<SettingTreeNode>(() => {
|
|
if (!defaultPanel) return settingCategories.value[0]
|
|
// Search through all groups in groupedMenuTreeNodes
|
|
for (const group of groupedMenuTreeNodes.value) {
|
|
const found = group.children?.find((node) => node.key === defaultPanel)
|
|
if (found) return found
|
|
}
|
|
return settingCategories.value[0]
|
|
})
|
|
|
|
const translateCategory = (node: SettingTreeNode) => ({
|
|
...node,
|
|
translatedLabel: t(
|
|
`settingsCategories.${normalizeI18nKey(node.label)}`,
|
|
node.label
|
|
)
|
|
})
|
|
|
|
const groupedMenuTreeNodes = computed<SettingTreeNode[]>(() => [
|
|
// Account settings - show different panels based on distribution and auth state
|
|
{
|
|
key: 'account',
|
|
label: 'Account',
|
|
children: [
|
|
userPanel.node,
|
|
...(isLoggedIn.value &&
|
|
isCloud &&
|
|
window.__CONFIG__?.subscription_required &&
|
|
subscriptionPanel
|
|
? [subscriptionPanel.node]
|
|
: []),
|
|
...(isLoggedIn.value &&
|
|
!(isCloud && window.__CONFIG__?.subscription_required)
|
|
? [creditsPanel.node]
|
|
: [])
|
|
].map(translateCategory)
|
|
},
|
|
// Normal settings stored in the settingStore
|
|
{
|
|
key: 'settings',
|
|
label: 'Application Settings',
|
|
children: settingCategories.value.map(translateCategory)
|
|
},
|
|
// Special settings such as about, keybinding, extension, server-config
|
|
{
|
|
key: 'specialSettings',
|
|
label: 'Special Settings',
|
|
children: [
|
|
keybindingPanel.node,
|
|
extensionPanel.node,
|
|
aboutPanel.node,
|
|
...(isElectron() ? [serverConfigPanel.node] : [])
|
|
].map(translateCategory)
|
|
}
|
|
])
|
|
|
|
onMounted(() => {
|
|
activeCategory.value = defaultCategory.value
|
|
})
|
|
|
|
return {
|
|
panels,
|
|
activeCategory,
|
|
defaultCategory,
|
|
groupedMenuTreeNodes,
|
|
settingCategories
|
|
}
|
|
}
|