White-label multi-domain tuki (Noxus Intra)

- CSS-muuttujat: kaikki kovakoodatut #0f3460/#16213e korvattu var(--primary-color)/var(--primary-dark)
- Uudet API-endpointit: branding (julkinen, domain-pohjainen), company_logo, company_logo_upload
- Domain-pohjainen brändäys: HTTP_HOST → yrityksen domains-arrayn matchaus
- Login: domain asettaa oletusyrityksen sessioon
- check_auth: palauttaa branding-objektin (primary_color, subtitle, logo_url)
- company_create/update: käsittelee domains, primary_color, subtitle, logo_file
- Dynaaminen login-sivu, header ja footer (logo, nimi, alaotsikko, värit)
- JS: loadBranding(), applyBranding(), yritysvaihdon brändäyspäivitys
- Admin-paneeli: brändäysasetukset (logo-upload, väri, alaotsikko, domainit)
- Git-repo siirretty intra.noxus.fi:hin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 13:44:49 +02:00
parent 918a5ff120
commit 095dc90b6f
4 changed files with 381 additions and 54 deletions

124
script.js
View File

@@ -136,6 +136,7 @@ async function checkAuth() {
availableCompanies = data.companies || [];
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
currentUserSignatures = data.signatures || {};
if (data.branding) applyBranding(data.branding);
showDashboard();
}
} catch (e) { /* not logged in */ }
@@ -171,6 +172,7 @@ document.getElementById('btn-logout').addEventListener('click', async () => {
document.getElementById('login-captcha').value = '';
showLoginView();
loadCaptcha();
loadBranding(); // Domain-pohjainen brändäys uudelleen
});
async function showDashboard() {
@@ -206,6 +208,11 @@ async function switchCompany(companyId) {
try {
await apiCall('company_switch', 'POST', { company_id: companyId });
currentCompany = availableCompanies.find(c => c.id === companyId) || null;
// Päivitä brändäys vaihdetun yrityksen mukaan
try {
const auth = await apiCall('check_auth');
if (auth.branding) applyBranding(auth.branding);
} catch (e2) {}
// Lataa uudelleen aktiivinen tab
const hash = window.location.hash.replace('#', '') || 'customers';
switchToTab(hash);
@@ -1837,6 +1844,20 @@ async function showCompanyDetail(id) {
const comp = companiesTabData.find(c => c.id === id);
document.getElementById('company-detail-title').textContent = (comp ? comp.nimi : id) + ' — Asetukset';
document.getElementById('company-edit-nimi').value = comp ? comp.nimi : '';
// Brändäyskentät
document.getElementById('company-edit-subtitle').value = comp?.subtitle || '';
const color = comp?.primary_color || '#0f3460';
document.getElementById('company-edit-color').value = color;
document.getElementById('company-edit-color-text').value = color;
document.getElementById('company-edit-domains').value = (comp?.domains || []).join('\n');
// Logo-esikatselu
const logoPreview = document.getElementById('company-logo-preview');
if (comp?.logo_file) {
logoPreview.src = 'api.php?action=company_logo&company_id=' + encodeURIComponent(id) + '&t=' + Date.now();
logoPreview.style.display = '';
} else {
logoPreview.style.display = 'none';
}
// Vaihda aktiivinen yritys jotta API-kutsut kohdistuvat oikein
await apiCall('company_switch', 'POST', { company_id: id });
@@ -1853,18 +1874,60 @@ document.getElementById('btn-company-back').addEventListener('click', () => {
renderCompaniesTable();
});
document.getElementById('btn-save-company-name').addEventListener('click', async () => {
// Synkronoi color picker <-> text input
document.getElementById('company-edit-color').addEventListener('input', function() {
document.getElementById('company-edit-color-text').value = this.value;
});
document.getElementById('company-edit-color-text').addEventListener('input', function() {
if (/^#[0-9a-fA-F]{6}$/.test(this.value)) {
document.getElementById('company-edit-color').value = this.value;
}
});
// Logo-upload
document.getElementById('company-logo-upload').addEventListener('change', async function() {
if (!this.files[0] || !currentCompanyDetail) return;
const formData = new FormData();
formData.append('logo', this.files[0]);
formData.append('company_id', currentCompanyDetail);
try {
const res = await fetch('api.php?action=company_logo_upload', { method: 'POST', body: formData, credentials: 'include' });
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Virhe');
// Päivitä preview
const preview = document.getElementById('company-logo-preview');
preview.src = data.logo_url + '&t=' + Date.now();
preview.style.display = '';
// Päivitä paikallinen data
const comp = companiesTabData.find(c => c.id === currentCompanyDetail);
if (comp) comp.logo_file = data.logo_file;
} catch (e) { alert(e.message); }
this.value = ''; // Reset file input
});
document.getElementById('btn-save-company-settings').addEventListener('click', async () => {
const nimi = document.getElementById('company-edit-nimi').value.trim();
if (!nimi) return;
const subtitle = document.getElementById('company-edit-subtitle').value.trim();
const primary_color = document.getElementById('company-edit-color').value;
const domainsText = document.getElementById('company-edit-domains').value;
const domains = domainsText.split('\n').map(d => d.trim()).filter(d => d);
try {
await apiCall('company_update', 'POST', { id: currentCompanyDetail, nimi });
alert('Nimi tallennettu!');
await apiCall('company_update', 'POST', { id: currentCompanyDetail, nimi, subtitle, primary_color, domains });
alert('Asetukset tallennettu!');
// Päivitä paikalliset tiedot
const comp = companiesTabData.find(c => c.id === currentCompanyDetail);
if (comp) comp.nimi = nimi;
if (comp) { comp.nimi = nimi; comp.subtitle = subtitle; comp.primary_color = primary_color; comp.domains = domains; }
const avail = availableCompanies.find(c => c.id === currentCompanyDetail);
if (avail) avail.nimi = nimi;
populateCompanySelector();
// Jos tämä on aktiivinen yritys → päivitä brändäys heti
if (currentCompany && currentCompany.id === currentCompanyDetail) {
applyBranding({
nimi, subtitle, primary_color,
logo_url: comp?.logo_file ? 'api.php?action=company_logo&company_id=' + encodeURIComponent(currentCompanyDetail) + '&t=' + Date.now() : ''
});
}
} catch (e) { alert(e.message); }
});
@@ -1985,6 +2048,59 @@ async function toggleCompanyUser(userId, companyId, add) {
} catch (e) { alert(e.message); }
}
// ==================== BRANDING ====================
function applyBranding(branding) {
const color = branding.primary_color || '#0f3460';
const nimi = branding.nimi || 'Noxus Intra';
const subtitle = branding.subtitle || '';
const logoUrl = branding.logo_url || '';
// CSS-muuttuja
document.documentElement.style.setProperty('--primary-color', color);
// Laske tumma variantti
document.documentElement.style.setProperty('--primary-dark', color);
// Login-sivu
const loginLogo = document.getElementById('login-logo');
const loginTitle = document.getElementById('login-title');
const loginSubtitle = document.getElementById('login-subtitle');
if (loginLogo) {
if (logoUrl) { loginLogo.src = logoUrl; loginLogo.style.display = ''; }
else { loginLogo.style.display = 'none'; }
}
if (loginTitle) loginTitle.textContent = nimi;
if (loginSubtitle) loginSubtitle.textContent = subtitle || 'Kirjaudu sisään';
// Muut login-boxien otsikot
document.querySelectorAll('.login-brand-title').forEach(el => el.textContent = nimi);
// Header
const headerLogo = document.getElementById('header-logo');
const headerIcon = document.getElementById('header-brand-icon');
const headerTitle = document.getElementById('header-title');
const headerSubtitle = document.getElementById('header-subtitle');
if (headerLogo) {
if (logoUrl) { headerLogo.src = logoUrl; headerLogo.style.display = ''; if (headerIcon) headerIcon.style.display = 'none'; }
else { headerLogo.style.display = 'none'; if (headerIcon) headerIcon.style.display = ''; }
}
if (headerTitle) headerTitle.textContent = nimi;
if (headerSubtitle) headerSubtitle.textContent = subtitle || 'Hallintapaneeli';
// Sivun title
document.title = nimi;
}
async function loadBranding() {
try {
const data = await apiCall('branding');
applyBranding(data);
} catch (e) {
// Oletusbrändäys
applyBranding({ nimi: 'Noxus Intra', primary_color: '#0f3460', subtitle: 'Hallintapaneeli', logo_url: '' });
}
}
// Init
loadBranding();
loadCaptcha();
checkAuth();