diff --git a/client/src/components/Dashboard.jsx b/client/src/components/Dashboard.jsx index d333e7a..5cdb2e5 100755 --- a/client/src/components/Dashboard.jsx +++ b/client/src/components/Dashboard.jsx @@ -1,193 +1 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import axios from 'axios'; -import CpasBadge, { getTier } from './CpasBadge'; -import EmployeeModal from './EmployeeModal'; -import AuditLog from './AuditLog'; - -const AT_RISK_THRESHOLD = 2; - -const TIERS = [ - { min: 0, max: 4 }, - { min: 5, max: 9 }, - { min: 10, max: 14 }, - { min: 15, max: 19 }, - { min: 20, max: 24 }, - { min: 25, max: 29 }, - { min: 30, max: 999 }, -]; - -function nextTierBoundary(points) { - for (const t of TIERS) { - if (points >= t.min && points <= t.max && t.max < 999) return t.max + 1; - } - return null; -} - -function isAtRisk(points) { - const boundary = nextTierBoundary(points); - return boundary !== null && (boundary - points) <= AT_RISK_THRESHOLD; -} - -const s = { - wrap: { padding: '32px 40px', color: '#f8f9fa' }, - header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', flexWrap: 'wrap', gap: '12px' }, - title: { fontSize: '24px', fontWeight: 700, color: '#f8f9fa' }, - subtitle: { fontSize: '13px', color: '#b5b5c0', marginTop: '3px' }, - statsRow: { display: 'flex', gap: '16px', flexWrap: 'wrap', marginBottom: '28px' }, - statCard: { flex: '1', minWidth: '140px', background: '#181924', border: '1px solid #30313f', borderRadius: '8px', padding: '16px', textAlign: 'center' }, - statNum: { fontSize: '28px', fontWeight: 800, color: '#f8f9fa' }, - statLbl: { fontSize: '11px', color: '#b5b5c0', marginTop: '4px' }, - search: { padding: '10px 14px', border: '1px solid #333544', borderRadius: '6px', fontSize: '14px', width: '260px', background: '#050608', color: '#f8f9fa' }, - table: { width: '100%', borderCollapse: 'collapse', background: '#111217', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 1px 8px rgba(0,0,0,0.6)', border: '1px solid #222' }, - th: { background: '#000000', color: '#f8f9fa', padding: '10px 14px', textAlign: 'left', fontSize: '12px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px' }, - td: { padding: '11px 14px', borderBottom: '1px solid #1c1d29', fontSize: '13px', verticalAlign: 'middle', color: '#f8f9fa' }, - nameBtn: { background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: '#d4af37', fontSize: '14px', padding: 0, textDecoration: 'underline dotted' }, - atRiskBadge: { display: 'inline-block', marginLeft: '8px', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, background: '#3b2e00', color: '#ffd666', border: '1px solid #d4af37', verticalAlign: 'middle' }, - zeroRow: { color: '#77798a', fontStyle: 'italic', fontSize: '12px' }, - toolbarRight: { display: 'flex', gap: '10px', alignItems: 'center' }, - refreshBtn: { padding: '9px 18px', background: '#d4af37', color: '#000', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' }, - auditBtn: { padding: '9px 18px', background: 'none', color: '#9ca0b8', border: '1px solid #2a2b3a', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' }, -}; - -export default function Dashboard() { - const [employees, setEmployees] = useState([]); - const [filtered, setFiltered] = useState([]); - const [search, setSearch] = useState(''); - const [selectedId, setSelectedId] = useState(null); - const [showAudit, setShowAudit] = useState(false); - const [loading, setLoading] = useState(true); - - const load = useCallback(() => { - setLoading(true); - axios.get('/api/dashboard') - .then(r => { setEmployees(r.data); setFiltered(r.data); }) - .finally(() => setLoading(false)); - }, []); - - useEffect(() => { load(); }, [load]); - - useEffect(() => { - const q = search.toLowerCase(); - setFiltered(employees.filter(e => - e.name.toLowerCase().includes(q) || - (e.department || '').toLowerCase().includes(q) || - (e.supervisor || '').toLowerCase().includes(q) - )); - }, [search, employees]); - - const atRiskCount = employees.filter(e => isAtRisk(e.active_points)).length; - const activeCount = employees.filter(e => e.active_points > 0).length; - const cleanCount = employees.filter(e => e.active_points === 0).length; - const maxPoints = employees.reduce((m, e) => Math.max(m, e.active_points), 0); - - return ( - <> -
-
-
-
Company Dashboard
-
Click any employee name to view their full profile
-
-
- setSearch(e.target.value)} - /> - - -
-
- -
-
-
{employees.length}
-
Total Employees
-
-
-
{cleanCount}
-
Elite Standing (0 pts)
-
-
-
{activeCount}
-
With Active Points
-
-
-
{atRiskCount}
-
At Risk (≤{AT_RISK_THRESHOLD} pts to next tier)
-
-
-
{maxPoints}
-
Highest Active Score
-
-
- - {loading ? ( -

Loading…

- ) : ( - - - - - - - - - - - - - - {filtered.length === 0 && ( - - - - )} - {filtered.map((emp, i) => { - const risk = isAtRisk(emp.active_points); - const tier = getTier(emp.active_points); - const boundary = nextTierBoundary(emp.active_points); - return ( - - - - - - - - - - ); - })} - -
#EmployeeDepartmentSupervisorTier / StandingActive Points90-Day Violations
- No employees found. -
{i + 1} - - {risk && ( - - ⚠ {boundary - emp.active_points} pt{boundary - emp.active_points > 1 ? 's' : ''} to {getTier(boundary).label.split('—')[0].trim()} - - )} - {emp.department || '—'}{emp.supervisor || '—'} - {emp.active_points} - {emp.violation_count}
- )} -
- - {selectedId && ( - { setSelectedId(null); load(); }} - /> - )} - {showAudit && setShowAudit(false)} />} - - ); -} +aW1wb3J0IFJlYWN0LCB7IHVzZVN0YXRlLCB1c2VFZmZlY3QsIHVzZUNhbGxiYWNrIH0gZnJvbSAncmVhY3QnOwppbXBvcnQgYXhpb3MgZnJvbSAnYXhpb3MnOwppbXBvcnQgQ3Bhc0JhZGdlLCB7IGdldFRpZXIgfSBmcm9tICcuL0NwYXNCYWRnZSc7CmltcG9ydCBFbXBsb3llZU1vZGFsIGZyb20gJy4vRW1wbG95ZWVNb2RhbCc7CmltcG9ydCBBdWRpdExvZyBmcm9tICcuL0F1ZGl0TG9nJzsKaW1wb3J0IEFuYWx5dGljc1BhbmVsIGZyb20gJy4vQW5hbHl0aWNzUGFuZWwnOwoKY29uc3QgQVRfUklTS19USFJFU0hPTEQgPSAyOwoKY29uc3QgVElFUlMgPSBbCiAgeyBtaW46IDAsICBtYXg6IDQgICB9LAogIHsgbWluOiA1LCAgbWF4OiA5ICAgfSwKICB7IG1pbjogMTAsIG1heDogMTQgIH0sCiAgeyBtaW46IDE1LCBtYXg6IDE5ICB9LAogIHsgbWluOiAyMCwgbWF4OiAyNCAgfSwKICB7IG1pbjogMjUsIG1heDogMjkgIH0sCiAgeyBtaW46IDMwLCBtYXg6IDk5OSB9LApdOwoKZnVuY3Rpb24gbmV4dFRpZXJCb3VuZGFyeShwb2ludHMpIHsKICBmb3IgKGNvbnN0IHQgb2YgVElFUlMpIHsKICAgIGlmIChwb2ludHMgPj0gdC5taW4gJiYgcG9pbnRzIDw9IHQubWF4ICYmIHQubWF4IDwgOTk5KSByZXR1cm4gdC5tYXggKyAxOwogIH0KICByZXR1cm4gbnVsbDsKfQoKZnVuY3Rpb24gaXNBdFJpc2socG9pbnRzKSB7CiAgY29uc3QgYm91bmRhcnkgPSBuZXh0VGllckJvdW5kYXJ5KHBvaW50cyk7CiAgcmV0dXJuIGJvdW5kYXJ5ICE9PSBudWxsICYmIChib3VuZGFyeSAtIHBvaW50cykgPD0gQVRfUklTS19USFJFU0hPTEQ7Cn0KCmNvbnN0IHMgPSB7CiAgd3JhcDogICAgICAgICB7IHBhZGRpbmc6ICczMnB4IDQwcHgnLCBjb2xvcjogJyNmOGY5ZmEnIH0sCiAgaGVhZGVyOiAgICAgICB7IGRpc3BsYXk6ICdmbGV4JywganVzdGlmeUNvbnRlbnQ6ICdzcGFjZS1iZXR3ZWVuJywgYWxpZ25JdGVtczogJ2NlbnRlcicsIG1hcmdpbkJvdHRvbTogJzI0cHgnLCBmbGV4V3JhcDogJ3dyYXAnLCBnYXA6ICcxMnB4JyB9LAogIHRpdGxlOiAgICAgICAgeyBmb250U2l6ZTogJzI0cHgnLCBmb250V2VpZ2h0OiA3MDAsIGNvbG9yOiAnI2Y4ZjlmYScgfSwKICBzdWJ0aXRsZTogICAgIHsgZm9udFNpemU6ICcxM3B4JywgY29sb3I6ICcjYjViNWMwJywgbWFyZ2luVG9wOiAnM3B4JyB9LAogIHN0YXRzUm93OiAgICAgeyBkaXNwbGF5OiAnZmxleCcsIGdhcDogJzE2cHgnLCBmbGV4V3JhcDogJ3dyYXAnLCBtYXJnaW5Cb3R0b206ICcyOHB4JyB9LAogIHN0YXRDYXJkOiAgICAgeyBmbGV4OiAnMScsIG1pbldpZHRoOiAnMTQwcHgnLCBiYWNrZ3JvdW5kOiAnIzE4MTkyNCcsIGJvcmRlcjogJzFweCBzb2xpZCAjMzAzMTNmJywgYm9yZGVyUmFkaXVzOiAnOHB4JywgcGFkZGluZzogJzE2cHgnLCB0ZXh0QWxpZ246ICdjZW50ZXInIH0sCiAgc3RhdE51bTogICAgICB7IGZvbnRTaXplOiAnMjhweCcsIGZvbnRXZWlnaHQ6IDgwMCwgY29sb3I6ICcjZjhmOWZhJyB9LAogIHN0YXRMYmw6ICAgICAgeyBmb250U2l6ZTogJzExcHgnLCBjb2xvcjogJyNiNWI1YzAnLCBtYXJnaW5Ub3A6ICc0cHgnIH0sCiAgc2VhcmNoOiAgICAgICB7IHBhZGRpbmc6ICcxMHB4IDE0cHgnLCBib3JkZXI6ICcxcHggc29saWQgIzMzMzU0NCcsIGJvcmRlclJhZGl1czogJzZweCcsIGZvbnRTaXplOiAnMTRweCcsIHdpZHRoOiAnMjIwcHgnLCBiYWNrZ3JvdW5kOiAnIzA1MDYwOCcsIGNvbG9yOiAnI2Y4ZjlmYScgfSwKICBzdXBlcnZpc29yU2VsZWN0OiB7IHBhZGRpbmc6ICcxMHB4IDE0cHgnLCBib3JkZXI6ICcxcHggc29saWQgIzMzMzU0NCcsIGJvcmRlclJhZGl1czogJzZweCcsIGZvbnRTaXplOiAnMTNweCcsIHdpZHRoOiAnMTkwcHgnLCBiYWNrZ3JvdW5kOiAnIzA1MDYwOCcsIGNvbG9yOiAnI2Y4ZjlmYScsIGZvbnRGYW1pbHk6ICdpbmhlcml0JyB9LAogIHRhYmxlOiAgICAgICAgeyB3aWR0aDogJzEwMCUnLCBib3JkZXJDb2xsYXBzZTogJ2NvbGxhcHNlJywgYmFja2dyb3VuZDogJyMxMTEyMTcnLCBib3JkZXJSYWRpdXM6ICc4cHgnLCBvdmVyZmxvdzogJ2hpZGRlbicsIGJveFNoYWRvdzogJzAgMXB4IDhweCByZ2JhKDAsMCwwLDAuNiknLCBib3JkZXI6ICcxcHggc29saWQgIzIyMicgfSwKICB0aDogICAgICAgICAgIHsgYmFja2dyb3VuZDogJyMwMDAwMDAnLCBjb2xvcjogJyNmOGY5ZmEnLCBwYWRkaW5nOiAnMTBweCAxNHB4JywgdGV4dEFsaWduOiAnbGVmdCcsIGZvbnRTaXplOiAnMTJweCcsIGZvbnRXZWlnaHQ6IDYwMCwgdGV4dFRyYW5zZm9ybTogJ3VwcGVyY2FzZScsIGxldHRlclNwYWNpbmc6ICcwLjVweCcgfSwKICB0ZDogICAgICAgICAgIHsgcGFkZGluZzogJzExcHggMTRweCcsIGJvcmRlckJvdHRvbTogJzFweCBzb2xpZCAjMWMxZDI5JywgZm9udFNpemU6ICcxM3B4JywgdmVydGljYWxBbGlnbjogJ21pZGRsZScsIGNvbG9yOiAnI2Y4ZjlmYScgfSwKICBuYW1lQnRuOiAgICAgIHsgYmFja2dyb3VuZDogJ25vbmUnLCBib3JkZXI6ICdub25lJywgY3Vyc29yOiAncG9pbnRlcicsIGZvbnRXZWlnaHQ6IDYwMCwgY29sb3I6ICcjZDRhZjM3JywgZm9udFNpemU6ICcxNHB4JywgcGFkZGluZzogMCwgdGV4dERlY29yYXRpb246ICd1bmRlcmxpbmUgZG90dGVkJyB9LAogIGF0Umlza0JhZGdlOiAgeyBkaXNwbGF5OiAnaW5saW5lLWJsb2NrJywgbWFyZ2luTGVmdDogJzhweCcsIHBhZGRpbmc6ICcycHggOHB4JywgYm9yZGVyUmFkaXVzOiAnMTBweCcsIGZvbnRTaXplOiAnMTBweCcsIGZvbnRXZWlnaHQ6IDcwMCwgYmFja2dyb3VuZDogJyMzYjJlMDAnLCBjb2xvcjogJyNmZmQ2NjYnLCBib3JkZXI6ICcxcHggc29saWQgI2Q0YWYzNycsIHZlcnRpY2FsQWxpZ246ICdtaWRkbGUnIH0sCiAgemVyb1JvdzogICAgICB7IGNvbG9yOiAnIzc3Nzk4YScsIGZvbnRTdHlsZTogJ2l0YWxpYycsIGZvbnRTaXplOiAnMTJweCcgfSwKICB0b29sYmFyUmlnaHQ6IHsgZGlzcGxheTogJ2ZsZXgnLCBnYXA6ICcxMHB4JywgYWxpZ25JdGVtczogJ2NlbnRlcicsIGZsZXhXcmFwOiAnd3JhcCcgfSwKICByZWZyZXNoQnRuOiAgIHsgcGFkZGluZzogJzlweCAxOHB4JywgYmFja2dyb3VuZDogJyNkNGFmMzcnLCBjb2xvcjogJyMwMDAnLCBib3JkZXI6ICdub25lJywgYm9yZGVyUmFkaXVzOiAnNnB4JywgY3Vyc29yOiAncG9pbnRlcicsIGZvbnRXZWlnaHQ6IDYwMCwgZm9udFNpemU6ICcxM3B4JyB9LAogIGF1ZGl0QnRuOiAgICAgeyBwYWRkaW5nOiAnOXB4IDE4cHgnLCBiYWNrZ3JvdW5kOiAnbm9uZScsIGNvbG9yOiAnIzljYTBiOCcsIGJvcmRlcjogJzFweCBzb2xpZCAjMmEyYjNhJywgYm9yZGVyUmFkaXVzOiAnNnB4JywgY3Vyc29yOiAncG9pbnRlcicsIGZvbnRXZWlnaHQ6IDYwMCwgZm9udFNpemU6ICcxM3B4JyB9LAogIGFuYWx5dGljc0J0bjogeyBwYWRkaW5nOiAnOXB4IDE4cHgnLCBiYWNrZ3JvdW5kOiAnbm9uZScsIGNvbG9yOiAnIzY0YjVmNicsIGJvcmRlcjogJzFweCBzb2xpZCAjMWE2MDkwJywgYm9yZGVyUmFkaXVzOiAnNnB4JywgY3Vyc29yOiAncG9pbnRlcicsIGZvbnRXZWlnaHQ6IDYwMCwgZm9udFNpemU6ICcxM3B4JyB9LAogIHN1cFRhZzogICAgICAgeyBkaXNwbGF5OiAnaW5saW5lLWJsb2NrJywgbWFyZ2luTGVmdDogJzhweCcsIHBhZGRpbmc6ICcycHggOHB4JywgYm9yZGVyUmFkaXVzOiAnMTBweCcsIGZvbnRTaXplOiAnMTFweCcsIGJhY2tncm91bmQ6ICcjMGExODMwJywgY29sb3I6ICcjNjRiNWY2JywgYm9yZGVyOiAnMXB4IHNvbGlkICMxYTYwOTAnLCB2ZXJ0aWNhbEFsaWduOiAnbWlkZGxlJyB9LAp9OwoKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gRGFzaGJvYXJkKCkgewogIGNvbnN0IFtlbXBsb3llZXMsICAgICBzZXRFbXBsb3llZXNdICAgICA9IHVzZVN0YXRlKFtdKTsKICBjb25zdCBbZmlsdGVyZWQsICAgICAgc2V0RmlsdGVyZWRdICAgICAgPSB1c2VTdGF0ZShbXSk7CiAgY29uc3QgW3NlYXJjaCwgICAgICAgIHNldFNlYXJjaF0gICAgICAgID0gdXNlU3RhdGUoJycpOwogIGNvbnN0IFtzZWxlY3RlZElkLCAgICBzZXRTZWxlY3RlZElkXSAgICA9IHVzZVN0YXRlKG51bGwpOwogIGNvbnN0IFtzaG93QXVkaXQsICAgICBzZXRTaG93QXVkaXRdICAgICA9IHVzZVN0YXRlKGZhbHNlKTsKICBjb25zdCBbc2hvd0FuYWx5dGljcywgc2V0U2hvd0FuYWx5dGljc10gPSB1c2VTdGF0ZShmYWxzZSk7CiAgY29uc3QgW2xvYWRpbmcsICAgICAgIHNldExvYWRpbmddICAgICAgID0gdXNlU3RhdGUodHJ1ZSk7CiAgY29uc3QgW3N1cGVydmlzb3JzLCAgIHNldFN1cGVydmlzb3JzXSAgID0gdXNlU3RhdGUoW10pOwogIGNvbnN0IFtzdXBGaWx0ZXIsICAgICBzZXRTdXBGaWx0ZXJdICAgICA9IHVzZVN0YXRlKCcnKTsKCiAgLy8gTG9hZCBzdXBlcnZpc29yIGxpc3Qgb25jZQogIHVzZUVmZmVjdCgoKSA9PiB7CiAgICBheGlvcy5nZXQoJy9hcGkvc3VwZXJ2aXNvcnMnKS50aGVuKHIgPT4gc2V0U3VwZXJ2aXNvcnMoci5kYXRhKSkuY2F0Y2goKCkgPT4ge30pOwogIH0sIFtdKTsKCiAgLy8gU3luYyBzdXBlcnZpc29yIGZpbHRlciB0by9mcm9tIFVSTCBwYXJhbXMgZm9yIGJvb2ttYXJrYWJpbGl0eQogIHVzZUVmZmVjdCgoKSA9PiB7CiAgICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHdpbmRvdy5sb2NhdGlvbi5zZWFyY2gpOwogICAgY29uc3QgcyA9IHBhcmFtcy5nZXQoJ3N1cGVydmlzb3InKTsKICAgIGlmIChzKSBzZXRTdXBGaWx0ZXIocyk7CiAgfSwgW10pOwoKICBjb25zdCBoYW5kbGVTdXBDaGFuZ2UgPSB2YWwgPT4gewogICAgc2V0U3VwRmlsdGVyKHZhbCk7CiAgICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHdpbmRvdy5sb2NhdGlvbi5zZWFyY2gpOwogICAgaWYgKHZhbCkgcGFyYW1zLnNldCgnc3VwZXJ2aXNvcicsIHZhbCk7CiAgICBlbHNlIHBhcmFtcy5kZWxldGUoJ3N1cGVydmlzb3InKTsKICAgIHdpbmRvdy5oaXN0b3J5LnJlcGxhY2VTdGF0ZShudWxsLCAnJywgYCR7d2luZG93LmxvY2F0aW9uLnBhdGhuYW1lfSR7cGFyYW1zLnRvU3RyaW5nKCkgPyAnPycgKyBwYXJhbXMudG9TdHJpbmcoKSA6ICcnfWApOwogIH07CgogIGNvbnN0IGxvYWQgPSB1c2VDYWxsYmFjaygoKSA9PiB7CiAgICBzZXRMb2FkaW5nKHRydWUpOwogICAgY29uc3QgcXVlcnkgPSBzdXBGaWx0ZXIgPyBgP3N1cGVydmlzb3I9JHtlbmNvZGVVUklDb21wb25lbnQoc3VwRmlsdGVyKX1gIDogJyc7CiAgICBheGlvcy5nZXQoYC9hcGkvZGFzaGJvYXJkJHtxdWVyeX1gKQogICAgICAudGhlbihyID0+IHsgc2V0RW1wbG95ZWVzKHIuZGF0YSk7IHNldEZpbHRlcmVkKHIuZGF0YSk7IH0pCiAgICAgIC5maW5hbGx5KCgpID0+IHNldExvYWRpbmcoZmFsc2UpKTsKICB9LCBbc3VwRmlsdGVyXSk7CgogIHVzZUVmZmVjdCgoKSA9PiB7IGxvYWQoKTsgfSwgW2xvYWRdKTsKCiAgdXNlRWZmZWN0KCgpID0+IHsKICAgIGNvbnN0IHEgPSBzZWFyY2gudG9Mb3dlckNhc2UoKTsKICAgIHNldEZpbHRlcmVkKGVtcGxveWVlcy5maWx0ZXIoZSA9PgogICAgICBlLm5hbWUudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSB8fAogICAgICAoZS5kZXBhcnRtZW50IHx8ICcnKS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKHEpIHx8CiAgICAgIChlLnN1cGVydmlzb3IgfHwgJycpLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSkKICAgICkpOwogIH0sIFtzZWFyY2gsIGVtcGxveWVlc10pOwoKICBjb25zdCBhdFJpc2tDb3VudCA9IGVtcGxveWVlcy5maWx0ZXIoZSA9PiBpc0F0UmlzayhlLmFjdGl2ZV9wb2ludHMpKS5sZW5ndGg7CiAgY29uc3QgYWN0aXZlQ291bnQgPSBlbXBsb3llZXMuZmlsdGVyKGUgPT4gZS5hY3RpdmVfcG9pbnRzID4gMCkubGVuZ3RoOwogIGNvbnN0IGNsZWFuQ291bnQgID0gZW1wbG95ZWVzLmZpbHRlcihlID0+IGUuYWN0aXZlX3BvaW50cyA9PT0gMCkubGVuZ3RoOwogIGNvbnN0IG1heFBvaW50cyAgID0gZW1wbG95ZWVzLnJlZHVjZSgobSwgZSkgPT4gTWF0aC5tYXgobSwgZS5hY3RpdmVfcG9pbnRzKSwgMCk7CgogIHJldHVybiAoCiAgICA8PgogICAgICA8ZGl2IHN0eWxlPXtzLndyYXB9PgogICAgICAgIDxkaXYgc3R5bGU9e3MuaGVhZGVyfT4KICAgICAgICAgIDxkaXY+CiAgICAgICAgICAgIDxkaXYgc3R5bGU9e3MudGl0bGV9PgogICAgICAgICAgICAgIHtzdXBGaWx0ZXIgPyBgJHtzdXBGaWx0ZXJ9J3MgVGVhbWAgOiAnQ29tcGFueSBEYXNoYm9hcmQnfQogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPGRpdiBzdHlsZT17cy5zdWJ0aXRsZX0+CiAgICAgICAgICAgICAge3N1cEZpbHRlciA/ICgKICAgICAgICAgICAgICAgIDxzcGFuPgogICAgICAgICAgICAgICAgICBGaWx0ZXJlZCB0byBzdXBlcnZpc29yOiA8c3Ryb25nIHN0eWxlPXt7IGNvbG9yOiAnIzY0YjVmNicgfX0+e3N1cEZpbHRlcn08L3N0cm9uZz4KICAgICAgICAgICAgICAgICAgPGJ1dHRvbgogICAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9eygpID0+IGhhbmRsZVN1cENoYW5nZSgnJyl9CiAgICAgICAgICAgICAgICAgICAgc3R5bGU9e3sgYmFja2dyb3VuZDogJ25vbmUnLCBib3JkZXI6ICdub25lJywgY29sb3I6ICcjNzc3OThhJywgY3Vyc29yOiAncG9pbnRlcicsIG1hcmdpbkxlZnQ6ICc4cHgnLCBmb250U2l6ZTogJzEycHgnIH19CiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICDilyUgY2xlYXIKICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICA8L3NwYW4+CiAgICAgICAgICAgICAgKSA6ICdDbGljayBhbnkgZW1wbG95ZWUgbmFtZSB0byB2aWV3IHRoZWlyIGZ1bGwgcHJvZmlsZSd9CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IHN0eWxlPXtzLnRvb2xiYXJSaWdodH0+CiAgICAgICAgICAgIHsvKiBTdXBlcnZpc29yIGZpbHRlciBkcm9wZG93biAqL30KICAgICAgICAgICAge3N1cGVydmlzb3JzLmxlbmd0aCA+IDAgJiYgKAogICAgICAgICAgICAgIDxzZWxlY3QKICAgICAgICAgICAgICAgIHN0eWxlPXtzLnN1cGVydmlzb3JTZWxlY3R9CiAgICAgICAgICAgICAgICB2YWx1ZT17c3VwRmlsdGVyfQogICAgICAgICAgICAgICAgb25DaGFuZ2U9e2UgPT4gaGFuZGxlU3VwQ2hhbmdlKGUudGFyZ2V0LnZhbHVlKX0KICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSIiPkFsbCBTdXBlcnZpc29yczwvb3B0aW9uPgogICAgICAgICAgICAgICAge3N1cGVydmlzb3JzLm1hcChzdXAgPT4gKAogICAgICAgICAgICAgICAgICA8b3B0aW9uIGtleT17c3VwfSB2YWx1ZT17c3VwfT57c3VwfTwvb3B0aW9uPgogICAgICAgICAgICAgICAgKSl9CiAgICAgICAgICAgICAgPC9zZWxlY3Q+CiAgICAgICAgICAgICl9CiAgICAgICAgICAgIDxpbnB1dAogICAgICAgICAgICAgIHN0eWxlPXtzLnNlYXJjaH0KICAgICAgICAgICAgICBwbGFjZWhvbGRlcj0iU2VhcmNoIG5hbWUsIGRlcHQu4oCmIgogICAgICAgICAgICAgIHZhbHVlPXtzZWFyY2h9CiAgICAgICAgICAgICAgb25DaGFuZ2U9e2UgPT4gc2V0U2VhcmNoKGUudGFyZ2V0LnZhbHVlKX0KICAgICAgICAgICAgLz4KICAgICAgICAgICAgPGJ1dHRvbiBzdHlsZT17cy5hbmFseXRpY3NCdG59IG9uQ2xpY2s9eygpID0+IHNldFNob3dBbmFseXRpY3ModHJ1ZSl9PgogICAgICAgICAgICAgIPCfk4YgQW5hbHl0aWNzCiAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICA8YnV0dG9uIHN0eWxlPXtzLmF1ZGl0QnRufSBvbkNsaWNrPXsoKSA9PiBzZXRTaG93QXVkaXQodHJ1ZSl9PvCfk4sgQXVkaXQgTG9nPC9idXR0b24+CiAgICAgICAgICAgIDxidXR0b24gc3R5bGU9e3MucmVmcmVzaEJ0bn0gb25DbGljaz17bG9hZH0+4oa7IFJlZnJlc2g8L2J1dHRvbj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgoKICAgICAgICA8ZGl2IHN0eWxlPXtzLnN0YXRzUm93fT4KICAgICAgICAgIDxkaXYgc3R5bGU9e3Muc3RhdENhcmR9PgogICAgICAgICAgICA8ZGl2IHN0eWxlPXtzLnN0YXROdW19PntlbXBsb3llZXMubGVuZ3RofTwvZGl2PgogICAgICAgICAgICA8ZGl2IHN0eWxlPXtzLnN0YXRMYmx9PntzdXBGaWx0ZXIgPyAnVGVhbSBNZW1iZXJzJyA6ICdUb3RhbCBFbXBsb3llZXMnfTwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IHN0eWxlPXt7IC4uLnMuc3RhdENhcmQsIGJvcmRlclRvcDogJzNweCBzb2xpZCAjMjhhNzQ1JyB9fT4KICAgICAgICAgICAgPGRpdiBzdHlsZT17eyAuLi5zLnN0YXROdW0sIGNvbG9yOiAnIzZlZTdiNycgfX0+e2NsZWFuQ291bnR9PC9kaXY+CiAgICAgICAgICAgIDxkaXYgc3R5bGU9e3Muc3RhdExibH0+RWxpdGUgU3RhbmRpbmcgKDAgcHRzKTwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IHN0eWxlPXt7IC4uLnMuc3RhdENhcmQsIGJvcmRlclRvcDogJzNweCBzb2xpZCAjZDRhZjM3JyB9fT4KICAgICAgICAgICAgPGRpdiBzdHlsZT17eyAuLi5zLnN0YXROdW0sIGNvbG9yOiAnI2ZmZDY2NicgfX0+e2FjdGl2ZUNvdW50fTwvZGl2PgogICAgICAgICAgICA8ZGl2IHN0eWxlPXtzLnN0YXRMYmx9PldpdGggQWN0aXZlIFBvaW50czwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IHN0eWxlPXt7IC4uLnMuc3RhdENhcmQsIGJvcmRlclRvcDogJzNweCBzb2xpZCAjZmZiMDIwJyB9fT4KICAgICAgICAgICAgPGRpdiBzdHlsZT17eyAuLi5zLnN0YXROdW0sIGNvbG9yOiAnI2ZmZGY4YScgfX0+e2F0Umlza0NvdW50fTwvZGl2PgogICAgICAgICAgICA8ZGl2IHN0eWxlPXtzLnN0YXRMYmx9PkF0IFJpc2sgKOKJpHtBVF9SSVNLX1RIUkVTSE9MRH0gcHRzIHRvIG5leHQgdGllcik8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBzdHlsZT17eyAuLi5zLnN0YXRDYXJkLCBib3JkZXJUb3A6ICczcHggc29saWQgI2MwMzkyYicgfX0+CiAgICAgICAgICAgIDxkaXYgc3R5bGU9e3sgLi4ucy5zdGF0TnVtLCBjb2xvcjogJyNmZjhhODAnIH19PnttYXhQb2ludHN9PC9kaXY+CiAgICAgICAgICAgIDxkaXYgc3R5bGU9e3Muc3RhdExibH0+SGlnaGVzdCBBY3RpdmUgU2NvcmU8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgoKICAgICAgICB7bG9hZGluZyA/ICgKICAgICAgICAgIDxwIHN0eWxlPXt7IGNvbG9yOiAnIzc3Nzk4YScsIHRleHRBbGlnbjogJ2NlbnRlcicsIHBhZGRpbmc6ICc0MHB4JyB9fT5Mb2FkaW5n4oCmPC9wPgogICAgICAgICkgOiAoCiAgICAgICAgICA8dGFibGUgc3R5bGU9e3MudGFibGV9PgogICAgICAgICAgICA8dGhlYWQ+CiAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgPHRoIHN0eWxlPXtzLnRofT4jPC90aD4KICAgICAgICAgICAgICAgIDx0aCBzdHlsZT17cy50aH0+RW1wbG95ZWU8L3RoPgogICAgICAgICAgICAgICAgPHRoIHN0eWxlPXtzLnRofT5EZXBhcnRtZW50PC90aD4KICAgICAgICAgICAgICAgIDx0aCBzdHlsZT17cy50aH0+U3VwZXJ2aXNvcjwvdGg+CiAgICAgICAgICAgICAgICA8dGggc3R5bGU9e3MudGh9PlRpZXIgLyBTdGFuZGluZzwvdGg+CiAgICAgICAgICAgICAgICA8dGggc3R5bGU9e3MudGh9PkFjdGl2ZSBQb2ludHM8L3RoPgogICAgICAgICAgICAgICAgPHRoIHN0eWxlPXtzLnRofT45MC1EYXkgVmlvbGF0aW9uczwvdGg+CiAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgIHtmaWx0ZXJlZC5sZW5ndGggPT09IDAgJiYgKAogICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICA8dGQgY29sU3Bhbj17N30gc3R5bGU9e3sgLi4ucy50ZCwgdGV4dEFsaWduOiAnY2VudGVyJywgLi4ucy56ZXJvUm93IH19PgogICAgICAgICAgICAgICAgICAgIHtzdXBGaWx0ZXIgPyBgTm8gZW1wbG95ZWVzIGZvdW5kIHVuZGVyICIke3N1cEZpbHRlcn0iLmAgOiAnTm8gZW1wbG95ZWVzIGZvdW5kLid9CiAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAge2ZpbHRlcmVkLm1hcCgoZW1wLCBpKSA9PiB7CiAgICAgICAgICAgICAgICBjb25zdCByaXNrICAgICA9IGlzQXRSaXNrKGVtcC5hY3RpdmVfcG9pbnRzKTsKICAgICAgICAgICAgICAgIGNvbnN0IHRpZXIgICAgID0gZ2V0VGllcihlbXAuYWN0aXZlX3BvaW50cyk7CiAgICAgICAgICAgICAgICBjb25zdCBib3VuZGFyeSA9IG5leHRUaWVyQm91bmRhcnkoZW1wLmFjdGl2ZV9wb2ludHMpOwogICAgICAgICAgICAgICAgcmV0dXJuICgKICAgICAgICAgICAgICAgICAgPHRyCiAgICAgICAgICAgICAgICAgICAga2V5PXtlbXAuaWR9CiAgICAgICAgICAgICAgICAgICAgc3R5bGU9e3sgYmFja2dyb3VuZDogcmlzayA/ICcjMTgxMjAwJyA6IGkgJSAyID09PSAwID8gJyMxMTEyMTcnIDogJyMxNTE2MjInIH19CiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9e3sgLi4ucy50ZCwgY29sb3I6ICcjNzc3OThhJywgZm9udFNpemU6ICcxMnB4JyB9fT57aSArIDF9PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9e3MudGR9PgogICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBzdHlsZT17cy5uYW1lQnRufSBvbkNsaWNrPXsoKSA9PiBzZXRTZWxlY3RlZElkKGVtcC5pZCl9PgogICAgICAgICAgICAgICAgICAgICAgICB7ZW1wLm5hbWV9CiAgICAgICAgICAgICAgICAgICAgICA8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgIHtyaXNrICYmICgKICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gc3R5bGU9e3MuYXRSaXNrQmFkZ2V9PgogICAgICAgICAgICAgICAgICAgICAgICAgIOKaoiB7Ym91bmRhcnkgLSBlbXAuYWN0aXZlX3BvaW50c30gcHR7Ym91bmRhcnkgLSBlbXAuYWN0aXZlX3BvaW50cyA+IDEgPyAncycgOiAnJ30gdG8ge2dldFRpZXIoYm91bmRhcnkpLmxhYmVsLnNwbGl0KCfigJQnKVswXS50cmltKCl9CiAgICAgICAgICAgICAgICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9e3sgLi4ucy50ZCwgY29sb3I6ICcjYzBjMmQ2JyB9fT57ZW1wLmRlcGFydG1lbnQgfHwgJ+KAlCd9PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9e3sgLi4ucy50ZCwgY29sb3I6ICcjYzBjMmQ2JyB9fT57ZW1wLnN1cGVydmlzb3IgfHwgJ+KAlCd9PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9e3MudGR9PjxDcGFzQmFkZ2UgcG9pbnRzPXtlbXAuYWN0aXZlX3BvaW50c30gLz48L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBzdHlsZT17eyAuLi5zLnRkLCBmb250V2VpZ2h0OiA3MDAsIGNvbG9yOiB0aWVyLmNvbG9yLCBmb250U2l6ZTogJzE2cHgnIH19PgogICAgICAgICAgICAgICAgICAgICAge2VtcC5hY3RpdmVfcG9pbnRzfQogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPXt7IC4uLnMudGQsIGNvbG9yOiAnI2MwYzJkNicgfX0+e2VtcC52aW9sYXRpb25fY291bnR9PC90ZD4KICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICk7CiAgICAgICAgICAgICAgfSl9CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgogICAgICAgICl9CiAgICAgIDwvZGl2PgoKICAgICAge3NlbGVjdGVkSWQgJiYgKAogICAgICAgIDxFbXBsb3llZU1vZGFsCiAgICAgICAgICBlbXBsb3llZUlkPXtzZWxlY3RlZElkfQogICAgICAgICAgb25DbG9zZT17KCkgPT4geyBzZXRTZWxlY3RlZElkKG51bGwpOyBsb2FkKCk7IH19CiAgICAgICAgLz4KICAgICAgKX0KICAgICAge3Nob3dBdWRpdCAmJiA8QXVkaXRMb2cgb25DbG9zZT17KCkgPT4gc2V0U2hvd0F1ZGl0KGZhbHNlKX0gLz59CiAgICAgIHtzaG93QW5hbHl0aWNzICYmIDxBbmFseXRpY3NQYW5lbCBvbkNsb3NlPXsoKSA9PiBzZXRTaG93QW5hbHl0aWNzKGZhbHNlKX0gLz59CiAgICA8Lz4KICApOwp9Cg== \ No newline at end of file