Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser
- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3) - Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor - Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS - Configurare adresă platformă externalizată (build args / .env / config.js) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
cec967f953
321 changed files with 80506 additions and 0 deletions
136
web/src/components/PipelineAnalysis/details/source.tsx
Normal file
136
web/src/components/PipelineAnalysis/details/source.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import React from 'react';
|
||||
import { ShieldAlert, AlertTriangle } from 'lucide-react';
|
||||
import { enumLabel } from '../../../utils/i18n-enums';
|
||||
import type { FindingAccent } from '../sections/FindingCard';
|
||||
import {
|
||||
FindingCardBox, FindingIconBox, FindingBody,
|
||||
FindingEyebrow, FindingHeadline, FindingSourceRow,
|
||||
ScoresBlock, ScoreLine, ScoreLineName, ScoreLineBar, ScoreLineFill, ScoreLineNum,
|
||||
} from '../styles';
|
||||
import { SectionDivider } from '../sections/SectionDivider';
|
||||
|
||||
const ACCENT_HEX: Record<FindingAccent, string> = {
|
||||
critical: '#ef4444',
|
||||
warning: '#f97316',
|
||||
info: '#3b82f6',
|
||||
success: '#22c55e',
|
||||
neutral: '#94a3b8',
|
||||
violet: '#7fd0d4',
|
||||
};
|
||||
|
||||
function trustAccent(trust: number): FindingAccent {
|
||||
if (trust >= 70) return 'success';
|
||||
if (trust >= 40) return 'warning';
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
function barColor(score: number): string {
|
||||
return score >= 70 ? '#22c55e' : score >= 40 ? '#f97316' : '#ef4444';
|
||||
}
|
||||
|
||||
export function renderSourceEditorial(data: any, isRo: boolean) {
|
||||
if (!data) return null;
|
||||
const trust = data.trust_score ?? 0;
|
||||
const accent = trustAccent(trust);
|
||||
const accentHex = ACCENT_HEX[accent];
|
||||
|
||||
const verdictLabel = enumLabel(data.verdict || '').replace(/_/g, ' ');
|
||||
const riskLevel = enumLabel(data.risk_level || '');
|
||||
|
||||
const pub = data.publication || {};
|
||||
const auth = data.author || {};
|
||||
const plat = data.platform || {};
|
||||
const dom = data.domain || {};
|
||||
const formula = data.formula || {};
|
||||
void formula;
|
||||
|
||||
const headline = isRo
|
||||
? `${verdictLabel} — încredere ${trust}/100${riskLevel ? ` · risc ${riskLevel.toLowerCase()}` : ''}`
|
||||
: `${verdictLabel} — trust ${trust}/100${riskLevel ? ` · ${riskLevel.toLowerCase()} risk` : ''}`;
|
||||
|
||||
const axes = [
|
||||
{
|
||||
name: isRo ? 'Publicație' : 'Publication',
|
||||
score: pub.score ?? 0,
|
||||
weight: Math.round((formula.publication_weight ?? 0.35) * 100),
|
||||
},
|
||||
{
|
||||
name: isRo ? 'Autor' : 'Author',
|
||||
score: auth.score ?? 0,
|
||||
weight: Math.round((formula.author_weight ?? 0.25) * 100),
|
||||
},
|
||||
{
|
||||
name: isRo ? 'Platformă' : 'Platform',
|
||||
score: plat.score ?? 0,
|
||||
weight: Math.round((formula.platform_weight ?? 0.15) * 100),
|
||||
},
|
||||
{
|
||||
name: isRo ? 'Domeniu' : 'Domain',
|
||||
score: dom.score ?? 0,
|
||||
weight: Math.round((formula.domain_weight ?? 0.25) * 100),
|
||||
},
|
||||
];
|
||||
|
||||
const flags: string[] = [];
|
||||
if (!pub.confirmed) flags.push(isRo ? 'fără publicație confirmată' : 'no confirmed publication');
|
||||
if (!auth.confirmed) flags.push(isRo ? 'fără autor identificat' : 'no identified author');
|
||||
if (!dom.name || dom.name === 'N/A') flags.push(isRo ? 'fără domeniu' : 'no domain');
|
||||
if (data.red_flags?.length) flags.push(...data.red_flags.map((f: string) => f.replace(/_/g, ' ').toLowerCase()));
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionDivider
|
||||
label={isRo ? 'Evaluarea sursei' : 'Source assessment'}
|
||||
count={`${trust} / 100 · ${verdictLabel.toLowerCase()}`}
|
||||
/>
|
||||
<FindingCardBox>
|
||||
<FindingIconBox accent={accentHex} lg>
|
||||
<ShieldAlert size={20} strokeWidth={2} />
|
||||
</FindingIconBox>
|
||||
<FindingBody>
|
||||
<FindingEyebrow>{verdictLabel}</FindingEyebrow>
|
||||
<FindingHeadline>{headline}</FindingHeadline>
|
||||
|
||||
<ScoresBlock style={{ marginTop: 4 }}>
|
||||
{axes.map((ax, i) => (
|
||||
<ScoreLine key={ax.name}>
|
||||
<ScoreLineName>{ax.name}</ScoreLineName>
|
||||
<ScoreLineBar>
|
||||
<ScoreLineFill width={ax.score} color={barColor(ax.score)} delay={400 + i * 60} />
|
||||
</ScoreLineBar>
|
||||
<ScoreLineNum>{ax.score}</ScoreLineNum>
|
||||
</ScoreLine>
|
||||
))}
|
||||
</ScoresBlock>
|
||||
|
||||
{flags.length > 0 && (
|
||||
<FindingSourceRow>
|
||||
<AlertTriangle size={13} strokeWidth={2} />
|
||||
{flags.map((f, idx) => (
|
||||
<React.Fragment key={idx}>
|
||||
{idx > 0 && <span className="sep">·</span>}
|
||||
<span>{f}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</FindingSourceRow>
|
||||
)}
|
||||
</FindingBody>
|
||||
</FindingCardBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderSourceDetail(data: any) {
|
||||
const isRo = (typeof document !== 'undefined' && document.documentElement.lang || '').toLowerCase().startsWith('ro');
|
||||
return <>{renderSourceEditorial(data, isRo)}</>;
|
||||
}
|
||||
|
||||
export function renderSourceSummary(data: any) {
|
||||
if (!data) return null;
|
||||
const trust = data.trust_score ?? 0;
|
||||
return (
|
||||
<span style={{ fontSize: 12, color: 'var(--fg-secondary)' }}>
|
||||
Trust {trust}% · {enumLabel(data.verdict || '').replace(/_/g, ' ')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue