mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-16 06:02:35 +02:00
Add web config
This commit is contained in:
+123
-85
@@ -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
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user