mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-14 03:42:45 +02:00
Enable provider
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
/* Provider Enable/Disable Styles */
|
||||
|
||||
/* Toggle Switch */
|
||||
.provider-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 26px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.provider-toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: 0.3s;
|
||||
border-radius: 26px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.provider-toggle input:checked + .toggle-slider {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
.provider-toggle input:checked + .toggle-slider:before {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
.provider-toggle input:disabled + .toggle-slider {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.toggle-slider.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toggle-slider.loading:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid white;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
/* Disabled Provider Card */
|
||||
.provider-card.disabled {
|
||||
opacity: 0.5;
|
||||
background: #f5f5f5;
|
||||
border-color: #ddd;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.provider-card.disabled::after {
|
||||
content: "DISABLED";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: var(--warning-color);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.provider-card.disabled:hover {
|
||||
transform: none;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.provider-card.disabled .provider-logo {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.provider-card.disabled .provider-info h3,
|
||||
.provider-card.disabled .provider-info {
|
||||
color: var(--gray-color);
|
||||
}
|
||||
|
||||
/* Hide forms and buttons when disabled */
|
||||
.provider-card.disabled .form-group,
|
||||
.provider-card.disabled .btn-group,
|
||||
.provider-card.disabled .no-credentials-required,
|
||||
.provider-card.disabled .status-indicator,
|
||||
.provider-card.disabled .token-expiration,
|
||||
.provider-card.disabled .auth-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Filter Section */
|
||||
.filter-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.filter-section label {
|
||||
font-weight: 600;
|
||||
margin-right: 10px;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 8px 35px 8px 12px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 0.95rem;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
|
||||
}
|
||||
|
||||
.filter-info {
|
||||
color: var(--gray-color);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-info strong {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.filter-section {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.provider-toggle {
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.provider-toggle input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Provider Enable/Disable Manager
|
||||
class ProviderEnableManager {
|
||||
constructor() {
|
||||
this.enabledStatus = {};
|
||||
this.currentFilter = 'all'; // 'all', 'enabled', 'disabled'
|
||||
}
|
||||
|
||||
async init(providers) {
|
||||
this.providers = providers;
|
||||
await this.loadEnabledStatus();
|
||||
}
|
||||
|
||||
async loadEnabledStatus() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/providers/enabled`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
this.enabledStatus = data.providers || {};
|
||||
console.log('Loaded enabled status:', this.enabledStatus);
|
||||
} catch (error) {
|
||||
console.error('Error loading enabled status:', error);
|
||||
// Initialize with defaults (all enabled)
|
||||
this.providers.forEach(p => {
|
||||
this.enabledStatus[p.name] = {
|
||||
enabled: true,
|
||||
source: 'default',
|
||||
can_modify: true
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isEnabled(providerName) {
|
||||
return this.enabledStatus[providerName]?.enabled !== false;
|
||||
}
|
||||
|
||||
canModify(providerName) {
|
||||
return this.enabledStatus[providerName]?.can_modify !== false;
|
||||
}
|
||||
|
||||
getSource(providerName) {
|
||||
return this.enabledStatus[providerName]?.source || 'default';
|
||||
}
|
||||
|
||||
async toggleProvider(providerName, newState) {
|
||||
const statusInfo = this.enabledStatus[providerName];
|
||||
|
||||
if (!statusInfo?.can_modify) {
|
||||
showAlert('error', `Cannot modify ${providerName} - controlled by ${statusInfo?.source || 'external source'}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/providers/${providerName}/enabled`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled: newState })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to update status');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Update local status
|
||||
this.enabledStatus[providerName] = {
|
||||
enabled: newState,
|
||||
source: result.source || 'file',
|
||||
can_modify: true
|
||||
};
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error toggling provider:', error);
|
||||
showAlert('error', `Failed to ${newState ? 'enable' : 'disable'} ${providerName}: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setFilter(filter) {
|
||||
this.currentFilter = filter;
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
applyFilter() {
|
||||
const cards = document.querySelectorAll('.provider-card');
|
||||
let visibleCount = 0;
|
||||
|
||||
cards.forEach(card => {
|
||||
const providerName = card.dataset.provider;
|
||||
const isEnabled = this.isEnabled(providerName);
|
||||
|
||||
let shouldShow = false;
|
||||
switch (this.currentFilter) {
|
||||
case 'all':
|
||||
shouldShow = true;
|
||||
break;
|
||||
case 'enabled':
|
||||
shouldShow = isEnabled;
|
||||
break;
|
||||
case 'disabled':
|
||||
shouldShow = !isEnabled;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldShow) {
|
||||
card.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Update filter info
|
||||
this.updateFilterInfo(visibleCount);
|
||||
}
|
||||
|
||||
updateFilterInfo(visibleCount) {
|
||||
const filterInfo = document.querySelector('.filter-info');
|
||||
if (filterInfo) {
|
||||
const totalEnabled = Object.values(this.enabledStatus).filter(s => s.enabled).length;
|
||||
const totalDisabled = Object.values(this.enabledStatus).filter(s => !s.enabled).length;
|
||||
|
||||
let infoText = '';
|
||||
switch (this.currentFilter) {
|
||||
case 'all':
|
||||
infoText = `Showing <strong>all ${visibleCount}</strong> providers (<strong>${totalEnabled}</strong> enabled, <strong>${totalDisabled}</strong> disabled)`;
|
||||
break;
|
||||
case 'enabled':
|
||||
infoText = `Showing <strong>${visibleCount}</strong> enabled providers`;
|
||||
break;
|
||||
case 'disabled':
|
||||
infoText = `Showing <strong>${visibleCount}</strong> disabled providers`;
|
||||
break;
|
||||
}
|
||||
filterInfo.innerHTML = infoText;
|
||||
}
|
||||
}
|
||||
|
||||
shouldShowInTab(providerName, tabId) {
|
||||
// In credentials tab, show all (enabled and disabled)
|
||||
if (tabId === 'credentials') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// In other tabs (proxy, epg-mapping), only show enabled
|
||||
return this.isEnabled(providerName);
|
||||
}
|
||||
|
||||
getFilteredProviders(tabId) {
|
||||
if (!this.providers) return [];
|
||||
|
||||
return this.providers.filter(provider =>
|
||||
this.shouldShowInTab(provider.name, tabId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create global instance
|
||||
window.providerEnableManager = new ProviderEnableManager();
|
||||
|
||||
// Add filter UI to credentials tab
|
||||
function addFilterUI() {
|
||||
const credentialsTab = document.getElementById('tab-credentials');
|
||||
const cardBody = credentialsTab.querySelector('.card-body');
|
||||
|
||||
// Check if filter already exists
|
||||
if (document.querySelector('.filter-section')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterHTML = `
|
||||
<div class="filter-section">
|
||||
<div>
|
||||
<label for="provider-filter">
|
||||
<i class="fas fa-filter"></i> Show:
|
||||
</label>
|
||||
<select id="provider-filter" class="filter-select">
|
||||
<option value="all">All Providers</option>
|
||||
<option value="enabled">Enabled Only</option>
|
||||
<option value="disabled">Disabled Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-info">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Insert after the alert
|
||||
const alert = cardBody.querySelector('.alert');
|
||||
if (alert) {
|
||||
alert.insertAdjacentHTML('afterend', filterHTML);
|
||||
} else {
|
||||
cardBody.insertAdjacentHTML('afterbegin', filterHTML);
|
||||
}
|
||||
|
||||
// Add event listener
|
||||
const filterSelect = document.getElementById('provider-filter');
|
||||
filterSelect.addEventListener('change', (e) => {
|
||||
window.providerEnableManager.setFilter(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Create toggle switch HTML
|
||||
function createToggleSwitch(providerName, isEnabled, canModify) {
|
||||
const toggleId = `toggle-${providerName}`;
|
||||
return `
|
||||
<label class="provider-toggle" title="${canModify ? 'Click to toggle' : 'Cannot modify - controlled externally'}">
|
||||
<input type="checkbox"
|
||||
id="${toggleId}"
|
||||
${isEnabled ? 'checked' : ''}
|
||||
${!canModify ? 'disabled' : ''}
|
||||
onchange="handleProviderToggle('${providerName}', this)">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
// Handle toggle change
|
||||
async function handleProviderToggle(providerName, checkbox) {
|
||||
const slider = checkbox.nextElementSibling;
|
||||
const card = checkbox.closest('.provider-card');
|
||||
|
||||
// Add loading state
|
||||
slider.classList.add('loading');
|
||||
checkbox.disabled = true;
|
||||
|
||||
const newState = checkbox.checked;
|
||||
const success = await window.providerEnableManager.toggleProvider(providerName, newState);
|
||||
|
||||
if (success) {
|
||||
// Update card appearance
|
||||
if (newState) {
|
||||
card.classList.remove('disabled');
|
||||
showAlert('success', `${providerName} enabled`);
|
||||
} else {
|
||||
card.classList.add('disabled');
|
||||
showAlert('success', `${providerName} disabled`);
|
||||
}
|
||||
|
||||
// Update filter info
|
||||
window.providerEnableManager.applyFilter();
|
||||
} else {
|
||||
// Revert checkbox on failure
|
||||
checkbox.checked = !newState;
|
||||
}
|
||||
|
||||
// Remove loading state
|
||||
slider.classList.remove('loading');
|
||||
checkbox.disabled = !window.providerEnableManager.canModify(providerName);
|
||||
}
|
||||
|
||||
// Export for global use
|
||||
window.handleProviderToggle = handleProviderToggle;
|
||||
window.addFilterUI = addFilterUI;
|
||||
window.createToggleSwitch = createToggleSwitch;
|
||||
Reference in New Issue
Block a user