Add web config

This commit is contained in:
Nirvana
2025-12-11 13:02:01 +01:00
parent 2468a1d655
commit f7bd59aadf
2 changed files with 271 additions and 120 deletions
+123 -85
View File
@@ -752,81 +752,105 @@
}
// Load proxy forms
function loadProxyForms() {
proxyContainer.innerHTML = providers.map(provider => `
<div class="provider-card" data-provider="${provider.name}">
<div class="provider-header">
${provider.logo ? `<img src="${provider.logo}" alt="${provider.label}" class="provider-logo">` : ''}
<div class="provider-info">
<h3>${provider.label}</h3>
<div class="provider-id">Proxy Configuration</div>
async function loadProxyForms() {
proxyContainer.innerHTML = await Promise.all(providers.map(async (provider) => {
try {
// Try to load existing proxy config
const proxyResponse = await fetch(`${API_BASE}/api/providers/${provider.name}/proxy`);
let existingProxy = null;
if (proxyResponse.ok) {
const proxyData = await proxyResponse.json();
if (proxyData.proxy_config) {
existingProxy = proxyData.proxy_config;
}
}
return `
<div class="provider-card" data-provider="${provider.name}">
<div class="provider-header">
${provider.logo ? `<img src="${provider.logo}" alt="${provider.label}" class="provider-logo">` : ''}
<div class="provider-info">
<h3>${provider.label}</h3>
<div class="provider-id">Proxy Configuration</div>
</div>
</div>
<div class="form-group">
<label for="proxy-host-${provider.name}">
<i class="fas fa-server"></i> Proxy Host
</label>
<input type="text"
id="proxy-host-${provider.name}"
class="form-control"
placeholder="proxy.example.com"
value="${existingProxy?.host || ''}">
</div>
<div class="form-group">
<label for="proxy-port-${provider.name}">
<i class="fas fa-plug"></i> Proxy Port
</label>
<input type="number"
id="proxy-port-${provider.name}"
class="form-control"
placeholder="8080"
min="1"
max="65535"
value="${existingProxy?.port || ''}">
</div>
<div class="form-group">
<label for="proxy-type-${provider.name}">
<i class="fas fa-network-wired"></i> Proxy Type
</label>
<select id="proxy-type-${provider.name}" class="form-control">
<option value="http" ${(!existingProxy || existingProxy.proxy_type === 'http') ? 'selected' : ''}>HTTP</option>
<option value="https" ${(existingProxy?.proxy_type === 'https') ? 'selected' : ''}>HTTPS</option>
<option value="socks4" ${(existingProxy?.proxy_type === 'socks4') ? 'selected' : ''}>SOCKS4</option>
<option value="socks5" ${(existingProxy?.proxy_type === 'socks5') ? 'selected' : ''}>SOCKS5</option>
</select>
</div>
<div class="form-group">
<label for="proxy-user-${provider.name}">
<i class="fas fa-user"></i> Username (Optional)
</label>
<input type="text"
id="proxy-user-${provider.name}"
class="form-control"
placeholder="proxyuser"
value="${existingProxy?.auth?.username || ''}">
</div>
<div class="form-group">
<label for="proxy-pass-${provider.name}">
<i class="fas fa-lock"></i> Password (Optional)
</label>
<input type="password"
id="proxy-pass-${provider.name}"
class="form-control"
placeholder="••••••••"
value="${existingProxy?.auth?.password || ''}">
</div>
<div class="btn-group">
<button onclick="saveProxy('${provider.name}')" class="btn btn-primary">
<i class="fas fa-save"></i> ${existingProxy ? 'Update' : 'Save'} Proxy
</button>
${existingProxy ? `
<button onclick="deleteProxy('${provider.name}')" class="btn btn-danger">
<i class="fas fa-trash"></i> Delete Proxy
</button>
` : ''}
</div>
</div>
<div class="form-group">
<label for="proxy-host-${provider.name}">
<i class="fas fa-server"></i> Proxy Host
</label>
<input type="text"
id="proxy-host-${provider.name}"
class="form-control"
placeholder="proxy.example.com">
</div>
<div class="form-group">
<label for="proxy-port-${provider.name}">
<i class="fas fa-plug"></i> Proxy Port
</label>
<input type="number"
id="proxy-port-${provider.name}"
class="form-control"
placeholder="8080"
min="1"
max="65535">
</div>
<div class="form-group">
<label for="proxy-type-${provider.name}">
<i class="fas fa-network-wired"></i> Proxy Type
</label>
<select id="proxy-type-${provider.name}" class="form-control">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
<option value="socks4">SOCKS4</option>
<option value="socks5">SOCKS5</option>
</select>
</div>
<div class="form-group">
<label for="proxy-user-${provider.name}">
<i class="fas fa-user"></i> Username (Optional)
</label>
<input type="text"
id="proxy-user-${provider.name}"
class="form-control"
placeholder="proxyuser">
</div>
<div class="form-group">
<label for="proxy-pass-${provider.name}">
<i class="fas fa-lock"></i> Password (Optional)
</label>
<input type="password"
id="proxy-pass-${provider.name}"
class="form-control"
placeholder="••••••••">
</div>
<div class="btn-group">
<button onclick="saveProxy('${provider.name}')" class="btn btn-primary">
<i class="fas fa-save"></i> Save Proxy
</button>
<button onclick="deleteProxy('${provider.name}')" class="btn btn-danger">
<i class="fas fa-trash"></i> Delete Proxy
</button>
</div>
</div>
`).join('');
`;
} catch (error) {
console.error(`Error loading proxy for ${provider.name}:`, error);
return ''; // Return empty string on error
}
})).then(htmls => htmls.join(''));
}
// Helper functions
@@ -863,9 +887,16 @@
// API Functions
async function saveCredentials(providerName) {
// Find the provider from the global providers array
const provider = providers.find(p => p.name === providerName);
if (!provider) {
showAlert('error', `Provider ${providerName} not found`);
return;
}
// Check if provider is client-only
if (!provider.requires_user_credentials) {
showAlert('info', `${providerName} uses hardcoded client credentials - no manual setup needed`);
showAlert('info', `${provider.label} uses hardcoded client credentials - no manual setup needed`);
return;
}
@@ -883,7 +914,9 @@
};
const statusEl = document.getElementById(`status-${providerName}`);
statusEl.innerHTML = '<span class="loader"></span> Saving...';
if (statusEl) {
statusEl.innerHTML = '<span class="loader"></span> Saving...';
}
try {
const response = await fetch(`${API_BASE}/api/providers/${providerName}/credentials`, {
@@ -895,26 +928,31 @@
const result = await response.json();
if (response.ok) {
statusEl.className = 'status-indicator status-success';
statusEl.innerHTML = '<i class="fas fa-check-circle"></i> Saved successfully';
if (statusEl) {
statusEl.className = 'status-indicator status-success';
statusEl.innerHTML = '<i class="fas fa-check-circle"></i> Saved successfully';
}
// Clear password field
// Clear password field for security
document.getElementById(`password-${providerName}`).value = '';
// Reload auth status
await loadAuthStatus();
renderCredentialsForms();
// Show success message
showAlert('success', `Credentials saved for ${providerName}`);
showAlert('success', `Credentials saved for ${provider.label}`);
} else {
statusEl.className = 'status-indicator status-error';
statusEl.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${result.error || 'Failed to save'}`;
showAlert('error', result.error || `Failed to save credentials for ${providerName}`);
if (statusEl) {
statusEl.className = 'status-indicator status-error';
statusEl.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${result.error || 'Failed to save'}`;
}
showAlert('error', result.error || `Failed to save credentials for ${provider.label}`);
}
} catch (error) {
statusEl.className = 'status-indicator status-error';
statusEl.innerHTML = '<i class="fas fa-exclamation-circle"></i> Network error';
if (statusEl) {
statusEl.className = 'status-indicator status-error';
statusEl.innerHTML = '<i class="fas fa-exclamation-circle"></i> Network error';
}
showAlert('error', `Network error: ${error.message}`);
console.error('Save error:', error);
}
+148 -35
View File
@@ -1235,6 +1235,58 @@ class UltimateService:
response.status = 500
return {'error': f'Internal server error: {str(api_err)}'}
@self.app.route('/api/providers/<provider>/credentials', method='GET')
def get_provider_credentials(provider):
"""
Get current credentials for a provider (masked for security)
Example: GET /api/providers/joyn/credentials
"""
try:
settings_manager = self._get_settings_manager()
# Parse provider and country
provider_name, country = settings_manager.parse_provider_country(provider)
# Get credentials
credentials = settings_manager.get_provider_credentials(provider_name, country)
if credentials:
# Return masked credentials for security
cred_dict = {
'has_credentials': True,
'credential_type': credentials.credential_type,
'username': getattr(credentials, 'username', None) if hasattr(credentials,
'username') else None,
'username_masked': None # We'll mask it
}
# Mask username if present (show first 2 chars and last 2 chars)
if cred_dict['username']:
username = cred_dict['username']
if len(username) > 4:
cred_dict['username_masked'] = username[:2] + '***' + username[-2:]
else:
cred_dict['username_masked'] = '***'
return {
'success': True,
'provider': provider,
'credentials': cred_dict
}
else:
return {
'success': True,
'provider': provider,
'credentials': None,
'message': 'No credentials found'
}
except Exception as api_err:
logger.error(f"API Error in GET /api/providers/{provider}/credentials: {str(api_err)}")
response.status = 500
return {'error': f'Internal server error: {str(api_err)}'}
@self.app.route('/api/providers/<provider>/credentials', method='POST')
def save_provider_credentials(provider):
"""
@@ -1250,28 +1302,34 @@ class UltimateService:
# Parse JSON body
try:
credentials_data = request.json
logger.info(f"Received credentials data for {provider}: {credentials_data}")
except Exception as json_err:
logger.error(f"Invalid JSON in request body: {json_err}")
response.status = 400
return {'error': 'Invalid JSON in request body'}
if not credentials_data:
logger.error("No credentials data provided")
response.status = 400
return {'error': 'Request body must contain credentials data'}
# Validate it's a dictionary
if not isinstance(credentials_data, dict):
logger.error(f"Credentials data is not a dict: {type(credentials_data)}")
response.status = 400
return {'error': 'Credentials data must be a JSON object'}
# Get settings manager
settings_manager = self._get_settings_manager()
logger.info(f"Settings manager obtained: {settings_manager}")
# Save credentials
success, message = settings_manager.save_provider_credentials_from_api(
provider, credentials_data
)
logger.info(f"Save result for {provider}: success={success}, message={message}")
if success:
response.status = 200
response.content_type = 'application/json; charset=utf-8'
@@ -1292,7 +1350,7 @@ class UltimateService:
return {'error': message}
except Exception as api_err:
logger.error(f"API Error in POST /api/providers/{provider}/credentials: {str(api_err)}")
logger.error(f"API Error in POST /api/providers/{provider}/credentials: {str(api_err)}", exc_info=True)
response.status = 500
return {'error': f'Internal server error: {str(api_err)}'}
@@ -1329,6 +1387,41 @@ class UltimateService:
response.status = 500
return {'error': f'Internal server error: {str(api_err)}'}
@self.app.route('/api/providers/<provider>/proxy', method='GET')
def get_provider_proxy(provider):
"""
Get current proxy configuration for a provider
Example: GET /api/providers/joyn/proxy
"""
try:
settings_manager = self._get_settings_manager()
# Parse provider and country
provider_name, country = settings_manager.parse_provider_country(provider)
# Get proxy config
proxy_config = settings_manager.get_provider_proxy(provider_name, country)
if proxy_config:
return {
'success': True,
'provider': provider,
'proxy_config': proxy_config.to_dict() if hasattr(proxy_config, 'to_dict') else proxy_config
}
else:
return {
'success': True,
'provider': provider,
'proxy_config': None,
'message': 'No proxy configuration found'
}
except Exception as api_err:
logger.error(f"API Error in GET /api/providers/{provider}/proxy: {str(api_err)}")
response.status = 500
return {'error': f'Internal server error: {str(api_err)}'}
@self.app.route('/api/providers/<provider>/proxy', method='POST')
def save_provider_proxy(provider):
"""
@@ -1438,30 +1531,18 @@ class UltimateService:
def export_config():
"""Export all configurations as JSON"""
try:
config = {
'credentials': {},
'proxy': {},
'settings': {}
}
settings_manager = self._get_settings_manager()
# Export credentials and proxy for all providers
providers = self.manager.list_providers()
for provider in providers:
# Get auth status (contains credential info)
status = self.manager.settings_manager.get_auth_status(provider)
if status.get('has_credentials'):
config['credentials'][provider] = status
# Use SettingsManager's export method
export_path = settings_manager.export_all_settings()
# Get proxy info
provider_instance = self.manager.get_provider(provider)
if hasattr(provider_instance, 'http_manager'):
proxy_config = getattr(provider_instance.http_manager.config, 'proxy_config', None)
if proxy_config:
config['proxy'][provider] = proxy_config
# Read the exported file
with open(export_path, 'r', encoding='utf-8') as f:
config_data = json.load(f)
response.content_type = 'application/json'
response.headers['Content-Disposition'] = 'attachment; filename="ultimate_backup.json"'
return json.dumps(config, indent=2)
response.headers['Content-Disposition'] = f'attachment; filename="{os.path.basename(export_path)}"'
return json.dumps(config_data, indent=2)
except Exception as e:
logger.error(f"Error exporting config: {e}")
@@ -1477,24 +1558,56 @@ class UltimateService:
response.status = 400
return {'error': 'No data provided'}
# First save the import data to a temp file
import tempfile
import uuid
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'import_{uuid.uuid4()}.json')
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(import_data, f)
# Use SettingsManager to import
settings_manager = self._get_settings_manager()
# Since SettingsManager doesn't have an import method, we'll process it manually
# Import credentials
credentials = import_data.get('credentials', {})
for provider, cred_data in credentials.items():
# Extract username/password from status data
if 'username' in cred_data and 'password' in cred_data:
self.manager.settings_manager.save_provider_credentials_from_api(
provider,
{'username': cred_data['username'], 'password': cred_data['password']}
credentials = import_data.get('providers', {})
imported_count = 0
for provider_name, provider_data in credentials.items():
# Extract credential data if available
if 'credentials' in provider_data:
cred_data = provider_data['credentials']
success, message = settings_manager.save_provider_credentials_from_api(
provider_name, cred_data
)
if success:
imported_count += 1
logger.info(f"Imported credentials for {provider_name}")
# Import proxy configs
proxies = import_data.get('proxy', {})
for provider, proxy_config in proxies.items():
self.manager.settings_manager.save_provider_proxy_from_api(
provider, proxy_config
)
# Import proxy data if available
if 'proxy' in provider_data:
proxy_data = provider_data['proxy']
success, message = settings_manager.save_provider_proxy_from_api(
provider_name, proxy_data
)
if success:
imported_count += 1
logger.info(f"Imported proxy for {provider_name}")
return {'success': True, 'imported': len(credentials) + len(proxies)}
# Clean up temp file
try:
os.remove(temp_file)
except:
pass
return {
'success': True,
'imported': imported_count,
'message': f'Imported {imported_count} configurations'
}
except Exception as e:
logger.error(f"Error importing config: {e}")