Add: Entra Cronjob, Cronjob-Verwaltung, Profilfotos, PDF-Auth-Fix, Tickets-Demo
This commit is contained in:
@@ -96,8 +96,8 @@ const relativeTime = (d) => {
|
||||
};
|
||||
|
||||
const isOnline = (agent) => {
|
||||
if (!agent || !agent.last_seen) return false;
|
||||
const diff = Date.now() - new Date(agent.last_seen).getTime();
|
||||
if (!agent || !agent.last_checkin) return false;
|
||||
const diff = Date.now() - new Date(agent.last_checkin).getTime();
|
||||
return diff < 5 * 60 * 1000;
|
||||
};
|
||||
|
||||
@@ -345,7 +345,7 @@ const AssetsPage = () => {
|
||||
try {
|
||||
const [aRes, agRes, stRes] = await Promise.all([
|
||||
authFetch(`${API}/assets`),
|
||||
authFetch(`${API}/monitoring/agents`),
|
||||
authFetch(`${API}/monitoring`),
|
||||
authFetch(`${API}/assets/stats`),
|
||||
]);
|
||||
const aData = aRes.ok ? await aRes.json() : {};
|
||||
@@ -383,9 +383,9 @@ const AssetsPage = () => {
|
||||
authFetch(`${API}/assets/${selectedId}/history`).then(r => r.ok ? r.json() : []),
|
||||
authFetch(`${API}/assets/${selectedId}/inspections`).then(r => r.ok ? r.json() : []),
|
||||
]).then(([d, h, ins]) => {
|
||||
setDetail(d);
|
||||
setHistory(Array.isArray(h) ? h : (h?.history || []));
|
||||
setInspections(Array.isArray(ins) ? ins : (ins?.inspections || []));
|
||||
setDetail(d?.data ?? d);
|
||||
setHistory(Array.isArray(h) ? h : (h?.data || h?.history || []));
|
||||
setInspections(Array.isArray(ins) ? ins : (ins?.data || ins?.inspections || []));
|
||||
}).catch(() => toast.error('Fehler beim Laden der Details'))
|
||||
.finally(() => setDetailLoading(false));
|
||||
}, [selectedId]);
|
||||
@@ -408,28 +408,40 @@ const AssetsPage = () => {
|
||||
if (statusFilter !== 'all' && a.status !== statusFilter) return false;
|
||||
if (!searchTerm) return true;
|
||||
const q = searchTerm.toLowerCase();
|
||||
if (searchMode === 'asset') {
|
||||
return (
|
||||
a.name?.toLowerCase().includes(q) ||
|
||||
a.serial_number?.toLowerCase().includes(q) ||
|
||||
a.inventory_number?.toLowerCase().includes(q) ||
|
||||
a.model?.toLowerCase().includes(q)
|
||||
);
|
||||
} else {
|
||||
return a.assigned_to_username?.toLowerCase().includes(q) ||
|
||||
a.assigned_to_name?.toLowerCase().includes(q);
|
||||
}
|
||||
return (
|
||||
a.name?.toLowerCase().includes(q) ||
|
||||
a.serial_number?.toLowerCase().includes(q) ||
|
||||
a.inventory_number?.toLowerCase().includes(q) ||
|
||||
a.model?.toLowerCase().includes(q) ||
|
||||
a.assigned_to_username?.toLowerCase().includes(q) ||
|
||||
a.assigned_to_name?.toLowerCase().includes(q) ||
|
||||
a.manufacturer?.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
/* ── Agent lookup by asset name ── */
|
||||
const getAgent = (assetName) => {
|
||||
if (!assetName || !agents.length) return null;
|
||||
/* ── Agent lookup: by serial, IP or hostname ── */
|
||||
const getAgent = (asset) => {
|
||||
if (!asset || !agents.length) return null;
|
||||
return agents.find(ag =>
|
||||
ag.hostname?.toLowerCase() === assetName.toLowerCase() ||
|
||||
ag.computer_name?.toLowerCase() === assetName.toLowerCase()
|
||||
(asset.serial_number && ag.hardware_serial && ag.hardware_serial.toLowerCase() === asset.serial_number.toLowerCase()) ||
|
||||
(asset.ip_address && ag.ip_address && ag.ip_address === asset.ip_address) ||
|
||||
(asset.name && ag.hostname && ag.hostname.toLowerCase() === asset.name.toLowerCase())
|
||||
) || null;
|
||||
};
|
||||
|
||||
/* ── PDF öffnen mit Auth-Token ── */
|
||||
const openPdf = async (url) => {
|
||||
try {
|
||||
const res = await authFetch(url);
|
||||
if (!res.ok) throw new Error('Fehler beim Laden');
|
||||
const blob = await res.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl, '_blank');
|
||||
} catch (e) {
|
||||
toast.error('PDF konnte nicht geöffnet werden: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
/* ── Actions ── */
|
||||
const handleUnassign = async () => {
|
||||
if (!detail) return;
|
||||
@@ -504,7 +516,7 @@ const AssetsPage = () => {
|
||||
}
|
||||
if (!detail) return null;
|
||||
|
||||
const agent = getAgent(detail.name);
|
||||
const agent = getAgent(detail);
|
||||
const online = isOnline(agent);
|
||||
const bookValue = calcBookValue(detail);
|
||||
const depPercent = calcDepPercent(detail);
|
||||
@@ -564,12 +576,12 @@ const AssetsPage = () => {
|
||||
{showMoreMenu && (
|
||||
<div className="ap-more-menu">
|
||||
<button className="ap-more-item" onClick={() => {
|
||||
window.open(`${API}/assets/${detail.id}/label`, '_blank');
|
||||
openPdf(`${API}/assets/${detail.id}/label`);
|
||||
setShowMoreMenu(false);
|
||||
}}>🏷️ Label drucken</button>
|
||||
{detail.status === 'zugewiesen' && (
|
||||
<button className="ap-more-item" onClick={() => {
|
||||
window.open(`${API}/assets/${detail.id}/handover-protocol`, '_blank');
|
||||
openPdf(`${API}/assets/${detail.id}/handover-protocol`);
|
||||
setShowMoreMenu(false);
|
||||
}}>📄 Übergabeprotokoll</button>
|
||||
)}
|
||||
@@ -620,7 +632,7 @@ const AssetsPage = () => {
|
||||
<div className={`ap-kpi-card ${online ? 'ap-kpi-card--green' : ''}`}>
|
||||
<div className="ap-kpi-label">🤖 Agent-Sync</div>
|
||||
<div className="ap-kpi-value" style={{ fontSize: 15 }}>
|
||||
{agent ? relativeTime(agent.last_seen) : '—'}
|
||||
{agent ? relativeTime(agent.last_checkin) : '—'}
|
||||
</div>
|
||||
<div className="ap-kpi-sub">
|
||||
{agent ? `v${agent.agent_version || '?'} · ${online ? 'aktiv' : 'offline'}` : 'Kein Agent'}
|
||||
@@ -783,7 +795,7 @@ const AssetsPage = () => {
|
||||
)}
|
||||
<div style={{ height: 8 }} />
|
||||
{[
|
||||
['Letzter Check-in', relativeTime(agent.last_seen), online ? '#34d399' : null],
|
||||
['Letzter Check-in', relativeTime(agent.last_checkin), online ? '#34d399' : null],
|
||||
['Agent-Version', agent.agent_version ? `v${agent.agent_version}` : '—'],
|
||||
['Hostname', agent.hostname || agent.computer_name || '—'],
|
||||
].map(([label, val, color]) => (
|
||||
@@ -1001,7 +1013,7 @@ const AssetsPage = () => {
|
||||
['Ausstehend', agent.pending_updates !== undefined ? `${agent.pending_updates} Updates` : undefined, agent.pending_updates > 0 ? '#f59e0b' : '#34d399'],
|
||||
['Letztes Update', agent.last_update_date ? fmtDate(agent.last_update_date) : undefined],
|
||||
['Agent-Version', agent.agent_version ? `v${agent.agent_version}` : undefined],
|
||||
['Letzter Check-in', relativeTime(agent.last_seen), online ? '#34d399' : null],
|
||||
['Letzter Check-in', relativeTime(agent.last_checkin), online ? '#34d399' : null],
|
||||
].map(([label, val, color]) => val !== undefined ? (
|
||||
<div key={label} className="ap-info-row">
|
||||
<span className="ap-info-label">{label}</span>
|
||||
@@ -1030,7 +1042,7 @@ const AssetsPage = () => {
|
||||
{detail.status === 'zugewiesen' && (
|
||||
<div
|
||||
className="ap-doc-item"
|
||||
onClick={() => window.open(`${API}/assets/${detail.id}/handover-protocol`, '_blank')}
|
||||
onClick={() => openPdf(`${API}/assets/${detail.id}/handover-protocol`)}
|
||||
>
|
||||
<div className="ap-doc-icon" style={{ background: 'rgba(52,211,153,0.1)' }}>📋</div>
|
||||
<div>
|
||||
@@ -1099,25 +1111,13 @@ const AssetsPage = () => {
|
||||
<span className="ap-panel-badge">{assets.length}</span>
|
||||
</div>
|
||||
|
||||
{/* Suchmodus Toggle */}
|
||||
<div className="ap-search-toggle">
|
||||
<button
|
||||
className={`ap-search-toggle-btn ${searchMode === 'asset' ? 'ap-search-toggle-btn--active' : ''}`}
|
||||
onClick={() => { setSearchMode('asset'); setSearchTerm(''); }}
|
||||
>🔍 Asset suchen</button>
|
||||
<button
|
||||
className={`ap-search-toggle-btn ${searchMode === 'user' ? 'ap-search-toggle-btn--active' : ''}`}
|
||||
onClick={() => { setSearchMode('user'); setSearchTerm(''); }}
|
||||
>👤 Benutzer suchen</button>
|
||||
</div>
|
||||
|
||||
{/* Suchfeld */}
|
||||
<div className="ap-search-box">
|
||||
<span className="ap-search-icon">{searchMode === 'user' ? '👤' : '🔍'}</span>
|
||||
<span className="ap-search-icon">🔍</span>
|
||||
<input
|
||||
className="ap-search-input"
|
||||
type="text"
|
||||
placeholder={searchMode === 'user' ? 'Benutzername…' : 'Suchen…'}
|
||||
placeholder="Asset, Benutzer, Seriennummer…"
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
@@ -1200,7 +1200,7 @@ const AssetsPage = () => {
|
||||
</div>
|
||||
)}
|
||||
{filteredAssets.map(asset => {
|
||||
const agent = getAgent(asset.name);
|
||||
const agent = getAgent(asset);
|
||||
const online = isOnline(agent);
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user