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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. 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
+ };