commons-proxy 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +757 -0
- package/bin/cli.js +146 -0
- package/package.json +97 -0
- package/public/Complaint Details.pdf +0 -0
- package/public/Cyber Crime Portal.pdf +0 -0
- package/public/app.js +229 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.png +0 -0
- package/public/index.html +549 -0
- package/public/js/components/account-manager.js +356 -0
- package/public/js/components/add-account-modal.js +414 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +605 -0
- package/public/js/components/dashboard/filters.js +362 -0
- package/public/js/components/dashboard/stats.js +110 -0
- package/public/js/components/dashboard.js +236 -0
- package/public/js/components/logs-viewer.js +100 -0
- package/public/js/components/models.js +36 -0
- package/public/js/components/server-config.js +349 -0
- package/public/js/config/constants.js +102 -0
- package/public/js/data-store.js +375 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +99 -0
- package/public/js/translations/en.js +367 -0
- package/public/js/translations/id.js +412 -0
- package/public/js/translations/pt.js +308 -0
- package/public/js/translations/tr.js +358 -0
- package/public/js/translations/zh.js +373 -0
- package/public/js/utils/account-actions.js +189 -0
- package/public/js/utils/error-handler.js +96 -0
- package/public/js/utils/model-config.js +42 -0
- package/public/js/utils/ui-logger.js +143 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/proxy-server-64.png +0 -0
- package/public/views/accounts.html +361 -0
- package/public/views/dashboard.html +484 -0
- package/public/views/logs.html +97 -0
- package/public/views/models.html +331 -0
- package/public/views/settings.html +1327 -0
- package/src/account-manager/credentials.js +378 -0
- package/src/account-manager/index.js +462 -0
- package/src/account-manager/onboarding.js +112 -0
- package/src/account-manager/rate-limits.js +369 -0
- package/src/account-manager/storage.js +160 -0
- package/src/account-manager/strategies/base-strategy.js +109 -0
- package/src/account-manager/strategies/hybrid-strategy.js +339 -0
- package/src/account-manager/strategies/index.js +79 -0
- package/src/account-manager/strategies/round-robin-strategy.js +76 -0
- package/src/account-manager/strategies/sticky-strategy.js +138 -0
- package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
- package/src/account-manager/strategies/trackers/index.js +9 -0
- package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +548 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +648 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +510 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +235 -0
- package/src/cloudcode/request-builder.js +93 -0
- package/src/cloudcode/session-manager.js +47 -0
- package/src/cloudcode/sse-parser.js +121 -0
- package/src/cloudcode/sse-streamer.js +293 -0
- package/src/cloudcode/streaming-handler.js +615 -0
- package/src/config.js +125 -0
- package/src/constants.js +407 -0
- package/src/errors.js +242 -0
- package/src/fallback-config.js +29 -0
- package/src/format/content-converter.js +193 -0
- package/src/format/index.js +20 -0
- package/src/format/request-converter.js +255 -0
- package/src/format/response-converter.js +120 -0
- package/src/format/schema-sanitizer.js +673 -0
- package/src/format/signature-cache.js +88 -0
- package/src/format/thinking-utils.js +648 -0
- package/src/index.js +148 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/providers/anthropic-provider.js +258 -0
- package/src/providers/base-provider.js +157 -0
- package/src/providers/cloudcode.js +94 -0
- package/src/providers/copilot.js +399 -0
- package/src/providers/github-provider.js +287 -0
- package/src/providers/google-provider.js +192 -0
- package/src/providers/index.js +211 -0
- package/src/providers/openai-compatible.js +265 -0
- package/src/providers/openai-provider.js +271 -0
- package/src/providers/openrouter-provider.js +325 -0
- package/src/providers/setup.js +83 -0
- package/src/server.js +870 -0
- package/src/utils/claude-config.js +245 -0
- package/src/utils/helpers.js +51 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/native-module-helper.js +162 -0
- package/src/webui/index.js +1134 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs Viewer Component
|
|
3
|
+
* Registers itself to window.Components for Alpine.js to consume
|
|
4
|
+
*/
|
|
5
|
+
window.Components = window.Components || {};
|
|
6
|
+
|
|
7
|
+
window.Components.logsViewer = () => ({
|
|
8
|
+
logs: [],
|
|
9
|
+
isAutoScroll: true,
|
|
10
|
+
eventSource: null,
|
|
11
|
+
searchQuery: '',
|
|
12
|
+
filters: {
|
|
13
|
+
INFO: true,
|
|
14
|
+
WARN: true,
|
|
15
|
+
ERROR: true,
|
|
16
|
+
SUCCESS: true,
|
|
17
|
+
DEBUG: false
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
get filteredLogs() {
|
|
21
|
+
const query = this.searchQuery.trim();
|
|
22
|
+
if (!query) {
|
|
23
|
+
return this.logs.filter(log => this.filters[log.level]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Try regex first, fallback to plain text search
|
|
27
|
+
let matcher;
|
|
28
|
+
try {
|
|
29
|
+
const regex = new RegExp(query, 'i');
|
|
30
|
+
matcher = (msg) => regex.test(msg);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// Invalid regex, fallback to case-insensitive string search
|
|
33
|
+
const lowerQuery = query.toLowerCase();
|
|
34
|
+
matcher = (msg) => msg.toLowerCase().includes(lowerQuery);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.logs.filter(log => {
|
|
38
|
+
// Level Filter
|
|
39
|
+
if (!this.filters[log.level]) return false;
|
|
40
|
+
|
|
41
|
+
// Search Filter
|
|
42
|
+
return matcher(log.message);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
init() {
|
|
47
|
+
this.startLogStream();
|
|
48
|
+
|
|
49
|
+
this.$watch('isAutoScroll', (val) => {
|
|
50
|
+
if (val) this.scrollToBottom();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Watch filters to maintain auto-scroll if enabled
|
|
54
|
+
this.$watch('searchQuery', () => { if(this.isAutoScroll) this.$nextTick(() => this.scrollToBottom()) });
|
|
55
|
+
this.$watch('filters', () => { if(this.isAutoScroll) this.$nextTick(() => this.scrollToBottom()) });
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
startLogStream() {
|
|
59
|
+
if (this.eventSource) this.eventSource.close();
|
|
60
|
+
|
|
61
|
+
const password = Alpine.store('global').webuiPassword;
|
|
62
|
+
const url = password
|
|
63
|
+
? `/api/logs/stream?history=true&password=${encodeURIComponent(password)}`
|
|
64
|
+
: '/api/logs/stream?history=true';
|
|
65
|
+
|
|
66
|
+
this.eventSource = new EventSource(url);
|
|
67
|
+
this.eventSource.onmessage = (event) => {
|
|
68
|
+
try {
|
|
69
|
+
const log = JSON.parse(event.data);
|
|
70
|
+
this.logs.push(log);
|
|
71
|
+
|
|
72
|
+
// Limit log buffer
|
|
73
|
+
const limit = Alpine.store('settings')?.logLimit || window.AppConstants.LIMITS.DEFAULT_LOG_LIMIT;
|
|
74
|
+
if (this.logs.length > limit) {
|
|
75
|
+
this.logs = this.logs.slice(-limit);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.isAutoScroll) {
|
|
79
|
+
this.$nextTick(() => this.scrollToBottom());
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
if (window.UILogger) window.UILogger.debug('Log parse error:', e.message);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
this.eventSource.onerror = () => {
|
|
87
|
+
if (window.UILogger) window.UILogger.debug('Log stream disconnected, reconnecting...');
|
|
88
|
+
setTimeout(() => this.startLogStream(), 3000);
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
scrollToBottom() {
|
|
93
|
+
const container = document.getElementById('logs-container');
|
|
94
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
clearLogs() {
|
|
98
|
+
this.logs = [];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Models Component
|
|
3
|
+
* Displays model quota/status list
|
|
4
|
+
* Registers itself to window.Components for Alpine.js to consume
|
|
5
|
+
*/
|
|
6
|
+
window.Components = window.Components || {};
|
|
7
|
+
|
|
8
|
+
window.Components.models = () => ({
|
|
9
|
+
init() {
|
|
10
|
+
// Ensure data is fetched when this tab becomes active (skip initial trigger)
|
|
11
|
+
this.$watch('$store.global.activeTab', (val, oldVal) => {
|
|
12
|
+
if (val === 'models' && oldVal !== undefined) {
|
|
13
|
+
// Trigger recompute to ensure filters are applied
|
|
14
|
+
this.$nextTick(() => {
|
|
15
|
+
Alpine.store('data').computeQuotaRows();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Initial compute if already on models tab
|
|
21
|
+
if (this.$store.global.activeTab === 'models') {
|
|
22
|
+
this.$nextTick(() => {
|
|
23
|
+
Alpine.store('data').computeQuotaRows();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Update model configuration (delegates to shared utility)
|
|
30
|
+
* @param {string} modelId - The model ID to update
|
|
31
|
+
* @param {object} configUpdates - Configuration updates (pinned, hidden)
|
|
32
|
+
*/
|
|
33
|
+
async updateModelConfig(modelId, configUpdates) {
|
|
34
|
+
return window.ModelConfigUtils.updateModelConfig(modelId, configUpdates);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Config Component
|
|
3
|
+
* Registers itself to window.Components for Alpine.js to consume
|
|
4
|
+
*/
|
|
5
|
+
window.Components = window.Components || {};
|
|
6
|
+
|
|
7
|
+
window.Components.serverConfig = () => ({
|
|
8
|
+
serverConfig: {},
|
|
9
|
+
loading: false,
|
|
10
|
+
advancedExpanded: false,
|
|
11
|
+
debounceTimers: {}, // Store debounce timers for each config field
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
// Initial fetch if this is the active sub-tab
|
|
15
|
+
if (this.activeTab === 'server') {
|
|
16
|
+
this.fetchServerConfig();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Watch local activeTab (from parent settings scope, skip initial trigger)
|
|
20
|
+
this.$watch('activeTab', (tab, oldTab) => {
|
|
21
|
+
if (tab === 'server' && oldTab !== undefined) {
|
|
22
|
+
this.fetchServerConfig();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async fetchServerConfig() {
|
|
28
|
+
const password = Alpine.store('global').webuiPassword;
|
|
29
|
+
try {
|
|
30
|
+
const { response, newPassword } = await window.utils.request('/api/config', {}, password);
|
|
31
|
+
if (newPassword) Alpine.store('global').webuiPassword = newPassword;
|
|
32
|
+
|
|
33
|
+
if (!response.ok) throw new Error('Failed to fetch config');
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
this.serverConfig = data.config || {};
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Failed to fetch server config:', e);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// Password management
|
|
44
|
+
passwordDialog: {
|
|
45
|
+
show: false,
|
|
46
|
+
oldPassword: '',
|
|
47
|
+
newPassword: '',
|
|
48
|
+
confirmPassword: ''
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
showPasswordDialog() {
|
|
52
|
+
this.passwordDialog = {
|
|
53
|
+
show: true,
|
|
54
|
+
oldPassword: '',
|
|
55
|
+
newPassword: '',
|
|
56
|
+
confirmPassword: ''
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
hidePasswordDialog() {
|
|
61
|
+
this.passwordDialog = {
|
|
62
|
+
show: false,
|
|
63
|
+
oldPassword: '',
|
|
64
|
+
newPassword: '',
|
|
65
|
+
confirmPassword: ''
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async changePassword() {
|
|
70
|
+
const store = Alpine.store('global');
|
|
71
|
+
const { oldPassword, newPassword, confirmPassword } = this.passwordDialog;
|
|
72
|
+
|
|
73
|
+
if (newPassword !== confirmPassword) {
|
|
74
|
+
store.showToast(store.t('passwordsNotMatch'), 'error');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (newPassword.length < 6) {
|
|
78
|
+
store.showToast(store.t('passwordTooShort'), 'error');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const { response } = await window.utils.request('/api/config/password', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({ oldPassword, newPassword })
|
|
87
|
+
}, store.webuiPassword);
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
throw new Error(data.error || store.t('failedToChangePassword'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Update stored password
|
|
95
|
+
store.webuiPassword = newPassword;
|
|
96
|
+
store.showToast(store.t('passwordChangedSuccess'), 'success');
|
|
97
|
+
this.hidePasswordDialog();
|
|
98
|
+
} catch (e) {
|
|
99
|
+
store.showToast(store.t('failedToChangePassword') + ': ' + e.message, 'error');
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Toggle Debug Mode with instant save
|
|
104
|
+
async toggleDebug(enabled) {
|
|
105
|
+
const store = Alpine.store('global');
|
|
106
|
+
|
|
107
|
+
// Optimistic update
|
|
108
|
+
const previousValue = this.serverConfig.debug;
|
|
109
|
+
this.serverConfig.debug = enabled;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const { response, newPassword } = await window.utils.request('/api/config', {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
115
|
+
body: JSON.stringify({ debug: enabled })
|
|
116
|
+
}, store.webuiPassword);
|
|
117
|
+
|
|
118
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
119
|
+
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
if (data.status === 'ok') {
|
|
122
|
+
const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus');
|
|
123
|
+
store.showToast(store.t('debugModeToggled', { status }), 'success');
|
|
124
|
+
await this.fetchServerConfig(); // Confirm server state
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error(data.error || store.t('failedToUpdateDebugMode'));
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// Rollback on error
|
|
130
|
+
this.serverConfig.debug = previousValue;
|
|
131
|
+
store.showToast(store.t('failedToUpdateDebugMode') + ': ' + e.message, 'error');
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Toggle Token Cache with instant save
|
|
136
|
+
async toggleTokenCache(enabled) {
|
|
137
|
+
const store = Alpine.store('global');
|
|
138
|
+
|
|
139
|
+
// Optimistic update
|
|
140
|
+
const previousValue = this.serverConfig.persistTokenCache;
|
|
141
|
+
this.serverConfig.persistTokenCache = enabled;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const { response, newPassword } = await window.utils.request('/api/config', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'Content-Type': 'application/json' },
|
|
147
|
+
body: JSON.stringify({ persistTokenCache: enabled })
|
|
148
|
+
}, store.webuiPassword);
|
|
149
|
+
|
|
150
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
151
|
+
|
|
152
|
+
const data = await response.json();
|
|
153
|
+
if (data.status === 'ok') {
|
|
154
|
+
const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus');
|
|
155
|
+
store.showToast(store.t('tokenCacheToggled', { status }), 'success');
|
|
156
|
+
await this.fetchServerConfig(); // Confirm server state
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error(data.error || store.t('failedToUpdateTokenCache'));
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Rollback on error
|
|
162
|
+
this.serverConfig.persistTokenCache = previousValue;
|
|
163
|
+
store.showToast(store.t('failedToUpdateTokenCache') + ': ' + e.message, 'error');
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// Generic debounced save method for numeric configs with validation
|
|
168
|
+
async saveConfigField(fieldName, value, displayName, validator = null) {
|
|
169
|
+
const store = Alpine.store('global');
|
|
170
|
+
|
|
171
|
+
// Validate input if validator provided
|
|
172
|
+
if (validator) {
|
|
173
|
+
const validation = window.Validators.validate(value, validator, true);
|
|
174
|
+
if (!validation.isValid) {
|
|
175
|
+
// Rollback to previous value
|
|
176
|
+
this.serverConfig[fieldName] = this.serverConfig[fieldName];
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
value = validation.value;
|
|
180
|
+
} else {
|
|
181
|
+
value = parseInt(value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Clear existing timer for this field
|
|
185
|
+
if (this.debounceTimers[fieldName]) {
|
|
186
|
+
clearTimeout(this.debounceTimers[fieldName]);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Optimistic update
|
|
190
|
+
const previousValue = this.serverConfig[fieldName];
|
|
191
|
+
this.serverConfig[fieldName] = value;
|
|
192
|
+
|
|
193
|
+
// Set new timer
|
|
194
|
+
this.debounceTimers[fieldName] = setTimeout(async () => {
|
|
195
|
+
try {
|
|
196
|
+
const payload = {};
|
|
197
|
+
payload[fieldName] = value;
|
|
198
|
+
|
|
199
|
+
const { response, newPassword } = await window.utils.request('/api/config', {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: { 'Content-Type': 'application/json' },
|
|
202
|
+
body: JSON.stringify(payload)
|
|
203
|
+
}, store.webuiPassword);
|
|
204
|
+
|
|
205
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
206
|
+
|
|
207
|
+
const data = await response.json();
|
|
208
|
+
if (data.status === 'ok') {
|
|
209
|
+
store.showToast(store.t('fieldUpdated', { displayName, value }), 'success');
|
|
210
|
+
await this.fetchServerConfig(); // Confirm server state
|
|
211
|
+
} else {
|
|
212
|
+
throw new Error(data.error || store.t('failedToUpdateField', { displayName }));
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {
|
|
215
|
+
// Rollback on error
|
|
216
|
+
this.serverConfig[fieldName] = previousValue;
|
|
217
|
+
store.showToast(store.t('failedToUpdateField', { displayName }) + ': ' + e.message, 'error');
|
|
218
|
+
}
|
|
219
|
+
}, window.AppConstants.INTERVALS.CONFIG_DEBOUNCE);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// Individual toggle methods for each Advanced Tuning field with validation
|
|
223
|
+
toggleMaxRetries(value) {
|
|
224
|
+
const { MAX_RETRIES_MIN, MAX_RETRIES_MAX } = window.AppConstants.VALIDATION;
|
|
225
|
+
this.saveConfigField('maxRetries', value, 'Max Retries',
|
|
226
|
+
(v) => window.Validators.validateRange(v, MAX_RETRIES_MIN, MAX_RETRIES_MAX, 'Max Retries'));
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
toggleRetryBaseMs(value) {
|
|
230
|
+
const { RETRY_BASE_MS_MIN, RETRY_BASE_MS_MAX } = window.AppConstants.VALIDATION;
|
|
231
|
+
this.saveConfigField('retryBaseMs', value, 'Retry Base Delay',
|
|
232
|
+
(v) => window.Validators.validateRange(v, RETRY_BASE_MS_MIN, RETRY_BASE_MS_MAX, 'Retry Base Delay'));
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
toggleRetryMaxMs(value) {
|
|
236
|
+
const { RETRY_MAX_MS_MIN, RETRY_MAX_MS_MAX } = window.AppConstants.VALIDATION;
|
|
237
|
+
this.saveConfigField('retryMaxMs', value, 'Retry Max Delay',
|
|
238
|
+
(v) => window.Validators.validateRange(v, RETRY_MAX_MS_MIN, RETRY_MAX_MS_MAX, 'Retry Max Delay'));
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
toggleDefaultCooldownMs(value) {
|
|
242
|
+
const { DEFAULT_COOLDOWN_MIN, DEFAULT_COOLDOWN_MAX } = window.AppConstants.VALIDATION;
|
|
243
|
+
this.saveConfigField('defaultCooldownMs', value, 'Default Cooldown',
|
|
244
|
+
(v) => window.Validators.validateTimeout(v, DEFAULT_COOLDOWN_MIN, DEFAULT_COOLDOWN_MAX));
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
toggleMaxWaitBeforeErrorMs(value) {
|
|
248
|
+
const { MAX_WAIT_MIN, MAX_WAIT_MAX } = window.AppConstants.VALIDATION;
|
|
249
|
+
this.saveConfigField('maxWaitBeforeErrorMs', value, 'Max Wait Threshold',
|
|
250
|
+
(v) => window.Validators.validateTimeout(v, MAX_WAIT_MIN, MAX_WAIT_MAX));
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
toggleMaxAccounts(value) {
|
|
254
|
+
const { MAX_ACCOUNTS_MIN, MAX_ACCOUNTS_MAX } = window.AppConstants.VALIDATION;
|
|
255
|
+
this.saveConfigField('maxAccounts', value, 'Max Accounts',
|
|
256
|
+
(v) => window.Validators.validateRange(v, MAX_ACCOUNTS_MIN, MAX_ACCOUNTS_MAX, 'Max Accounts'));
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
toggleRateLimitDedupWindowMs(value) {
|
|
260
|
+
const { RATE_LIMIT_DEDUP_MIN, RATE_LIMIT_DEDUP_MAX } = window.AppConstants.VALIDATION;
|
|
261
|
+
this.saveConfigField('rateLimitDedupWindowMs', value, 'Rate Limit Dedup Window',
|
|
262
|
+
(v) => window.Validators.validateTimeout(v, RATE_LIMIT_DEDUP_MIN, RATE_LIMIT_DEDUP_MAX));
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
toggleMaxConsecutiveFailures(value) {
|
|
266
|
+
const { MAX_CONSECUTIVE_FAILURES_MIN, MAX_CONSECUTIVE_FAILURES_MAX } = window.AppConstants.VALIDATION;
|
|
267
|
+
this.saveConfigField('maxConsecutiveFailures', value, 'Max Consecutive Failures',
|
|
268
|
+
(v) => window.Validators.validateRange(v, MAX_CONSECUTIVE_FAILURES_MIN, MAX_CONSECUTIVE_FAILURES_MAX, 'Max Consecutive Failures'));
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
toggleExtendedCooldownMs(value) {
|
|
272
|
+
const { EXTENDED_COOLDOWN_MIN, EXTENDED_COOLDOWN_MAX } = window.AppConstants.VALIDATION;
|
|
273
|
+
this.saveConfigField('extendedCooldownMs', value, 'Extended Cooldown',
|
|
274
|
+
(v) => window.Validators.validateTimeout(v, EXTENDED_COOLDOWN_MIN, EXTENDED_COOLDOWN_MAX));
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
toggleMaxCapacityRetries(value) {
|
|
278
|
+
const { MAX_CAPACITY_RETRIES_MIN, MAX_CAPACITY_RETRIES_MAX } = window.AppConstants.VALIDATION;
|
|
279
|
+
this.saveConfigField('maxCapacityRetries', value, 'Max Capacity Retries',
|
|
280
|
+
(v) => window.Validators.validateRange(v, MAX_CAPACITY_RETRIES_MIN, MAX_CAPACITY_RETRIES_MAX, 'Max Capacity Retries'));
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// Toggle Account Selection Strategy
|
|
284
|
+
async toggleStrategy(strategy) {
|
|
285
|
+
const store = Alpine.store('global');
|
|
286
|
+
const validStrategies = ['sticky', 'round-robin', 'hybrid'];
|
|
287
|
+
|
|
288
|
+
if (!validStrategies.includes(strategy)) {
|
|
289
|
+
store.showToast(store.t('invalidStrategy'), 'error');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Optimistic update
|
|
294
|
+
const previousValue = this.serverConfig.accountSelection?.strategy || 'hybrid';
|
|
295
|
+
if (!this.serverConfig.accountSelection) {
|
|
296
|
+
this.serverConfig.accountSelection = {};
|
|
297
|
+
}
|
|
298
|
+
this.serverConfig.accountSelection.strategy = strategy;
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const { response, newPassword } = await window.utils.request('/api/config', {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: { 'Content-Type': 'application/json' },
|
|
304
|
+
body: JSON.stringify({ accountSelection: { strategy } })
|
|
305
|
+
}, store.webuiPassword);
|
|
306
|
+
|
|
307
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
308
|
+
|
|
309
|
+
const data = await response.json();
|
|
310
|
+
if (data.status === 'ok') {
|
|
311
|
+
const strategyLabel = this.getStrategyLabel(strategy);
|
|
312
|
+
store.showToast(store.t('strategyUpdated', { strategy: strategyLabel }), 'success');
|
|
313
|
+
await this.fetchServerConfig(); // Confirm server state
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error(data.error || store.t('failedToUpdateStrategy'));
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
// Rollback on error
|
|
319
|
+
if (!this.serverConfig.accountSelection) {
|
|
320
|
+
this.serverConfig.accountSelection = {};
|
|
321
|
+
}
|
|
322
|
+
this.serverConfig.accountSelection.strategy = previousValue;
|
|
323
|
+
store.showToast(store.t('failedToUpdateStrategy') + ': ' + e.message, 'error');
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// Get display label for a strategy
|
|
328
|
+
getStrategyLabel(strategy) {
|
|
329
|
+
const store = Alpine.store('global');
|
|
330
|
+
const labels = {
|
|
331
|
+
'sticky': store.t('strategyStickyLabel'),
|
|
332
|
+
'round-robin': store.t('strategyRoundRobinLabel'),
|
|
333
|
+
'hybrid': store.t('strategyHybridLabel')
|
|
334
|
+
};
|
|
335
|
+
return labels[strategy] || strategy;
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
// Get description for current strategy
|
|
339
|
+
currentStrategyDescription() {
|
|
340
|
+
const store = Alpine.store('global');
|
|
341
|
+
const strategy = this.serverConfig.accountSelection?.strategy || 'hybrid';
|
|
342
|
+
const descriptions = {
|
|
343
|
+
'sticky': store.t('strategyStickyDesc'),
|
|
344
|
+
'round-robin': store.t('strategyRoundRobinDesc'),
|
|
345
|
+
'hybrid': store.t('strategyHybridDesc')
|
|
346
|
+
};
|
|
347
|
+
return descriptions[strategy] || '';
|
|
348
|
+
}
|
|
349
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Constants
|
|
3
|
+
* Centralized configuration values and magic numbers
|
|
4
|
+
*/
|
|
5
|
+
window.AppConstants = window.AppConstants || {};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Time intervals (in milliseconds)
|
|
9
|
+
*/
|
|
10
|
+
window.AppConstants.INTERVALS = {
|
|
11
|
+
// Dashboard refresh interval (5 minutes)
|
|
12
|
+
DASHBOARD_REFRESH: 300000,
|
|
13
|
+
|
|
14
|
+
// OAuth message handler timeout (5 minutes)
|
|
15
|
+
OAUTH_MESSAGE_TIMEOUT: 300000,
|
|
16
|
+
|
|
17
|
+
// Server config debounce delay
|
|
18
|
+
CONFIG_DEBOUNCE: 500,
|
|
19
|
+
|
|
20
|
+
// General short delay (for UI transitions)
|
|
21
|
+
SHORT_DELAY: 2000
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Data limits and quotas
|
|
26
|
+
*/
|
|
27
|
+
window.AppConstants.LIMITS = {
|
|
28
|
+
// Default log limit
|
|
29
|
+
DEFAULT_LOG_LIMIT: 2000,
|
|
30
|
+
|
|
31
|
+
// Minimum quota value
|
|
32
|
+
MIN_QUOTA: 100,
|
|
33
|
+
|
|
34
|
+
// Percentage base (for calculations)
|
|
35
|
+
PERCENTAGE_BASE: 100
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validation ranges
|
|
40
|
+
*/
|
|
41
|
+
window.AppConstants.VALIDATION = {
|
|
42
|
+
// Port range
|
|
43
|
+
PORT_MIN: 1,
|
|
44
|
+
PORT_MAX: 65535,
|
|
45
|
+
|
|
46
|
+
// Timeout range (0 - 5 minutes)
|
|
47
|
+
TIMEOUT_MIN: 0,
|
|
48
|
+
TIMEOUT_MAX: 300000,
|
|
49
|
+
|
|
50
|
+
// Log limit range
|
|
51
|
+
LOG_LIMIT_MIN: 100,
|
|
52
|
+
LOG_LIMIT_MAX: 10000,
|
|
53
|
+
|
|
54
|
+
// Retry configuration ranges
|
|
55
|
+
MAX_RETRIES_MIN: 0,
|
|
56
|
+
MAX_RETRIES_MAX: 20,
|
|
57
|
+
|
|
58
|
+
RETRY_BASE_MS_MIN: 100,
|
|
59
|
+
RETRY_BASE_MS_MAX: 10000,
|
|
60
|
+
|
|
61
|
+
RETRY_MAX_MS_MIN: 1000,
|
|
62
|
+
RETRY_MAX_MS_MAX: 60000,
|
|
63
|
+
|
|
64
|
+
// Cooldown range (0 - 10 minutes)
|
|
65
|
+
DEFAULT_COOLDOWN_MIN: 0,
|
|
66
|
+
DEFAULT_COOLDOWN_MAX: 600000,
|
|
67
|
+
|
|
68
|
+
// Max wait threshold (1 - 30 minutes)
|
|
69
|
+
MAX_WAIT_MIN: 60000,
|
|
70
|
+
MAX_WAIT_MAX: 1800000,
|
|
71
|
+
|
|
72
|
+
// Max accounts range (1 - 100)
|
|
73
|
+
MAX_ACCOUNTS_MIN: 1,
|
|
74
|
+
MAX_ACCOUNTS_MAX: 100,
|
|
75
|
+
|
|
76
|
+
// Rate limit dedup window (1 - 30 seconds)
|
|
77
|
+
RATE_LIMIT_DEDUP_MIN: 1000,
|
|
78
|
+
RATE_LIMIT_DEDUP_MAX: 30000,
|
|
79
|
+
|
|
80
|
+
// Consecutive failures (1 - 10)
|
|
81
|
+
MAX_CONSECUTIVE_FAILURES_MIN: 1,
|
|
82
|
+
MAX_CONSECUTIVE_FAILURES_MAX: 10,
|
|
83
|
+
|
|
84
|
+
// Extended cooldown (10 seconds - 5 minutes)
|
|
85
|
+
EXTENDED_COOLDOWN_MIN: 10000,
|
|
86
|
+
EXTENDED_COOLDOWN_MAX: 300000,
|
|
87
|
+
|
|
88
|
+
// Capacity retries (1 - 10)
|
|
89
|
+
MAX_CAPACITY_RETRIES_MIN: 1,
|
|
90
|
+
MAX_CAPACITY_RETRIES_MAX: 10
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* UI Constants
|
|
95
|
+
*/
|
|
96
|
+
window.AppConstants.UI = {
|
|
97
|
+
// Toast auto-dismiss duration
|
|
98
|
+
TOAST_DURATION: 3000,
|
|
99
|
+
|
|
100
|
+
// Loading spinner delay
|
|
101
|
+
LOADING_DELAY: 200
|
|
102
|
+
};
|