Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"4000"
],
"envFile": "${workspaceFolder}/backend/.env",
"preLaunchTask": "docker:mongo:start",
"postDebugTask": "docker:mongo:stop",
"console": "internalConsole",
"justMyCode": true
}
Expand Down
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@
}
}
},
{
"label": "docker:mongo:start",
"type": "shell",
"command": "docker compose -f ${workspaceFolder}/docker-compose.yml up -d mongo",
"problemMatcher": []
},
{
"label": "docker:mongo:stop",
"type": "shell",
"command": "docker compose -f ${workspaceFolder}/docker-compose.yml stop mongo",
"problemMatcher": []
},
{
"label": "mcp:install",
"type": "shell",
Expand Down
168 changes: 98 additions & 70 deletions apps/web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ImportModal from './components/ImportModal.jsx';
import UploadModal from './components/UploadModal.jsx';
import RepoModal from './components/RepoModal.jsx';
import LibrarySummary from './components/LibrarySummary.jsx';
import RepoScans from './components/RepoScans.jsx';
import heroLogo from './assets/logo.png';

export default function App() {
Expand All @@ -15,6 +16,7 @@ export default function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
const [isRepoModalOpen, setIsRepoModalOpen] = useState(false);
const [activePage, setActivePage] = useState('home'); // home | repo-scans
const menuRef = useRef(null);

const loadLibraries = async () => {
Expand Down Expand Up @@ -60,88 +62,114 @@ export default function App() {
return (
<div className="app-shell">
<header className="hero">
<a href="/" className="hero-logo-link">
<img src={heroLogo} alt="LicenGuard" className="hero-logo" />
</a>
<div>
<h1>LicenGuard</h1>
<p>License Intelligence Platform</p>
<div className="hero__brand">
<a href="/" className="hero-logo-link">
<img src={heroLogo} alt="LicenGuard" className="hero-logo" />
</a>
<div>
<h1>LicenGuard</h1>
<p>License Intelligence Platform</p>
</div>
</div>
</header>

<section className="search-row">
<div className="search-bar">
<input
type="search"
placeholder="Search libraries or versions..."
value={query}
onChange={event => setQuery(event.target.value)}
/>
</div>
<div className="inline-add-wrapper" ref={menuRef}>
<nav className="hero-nav">
<button
className="inline-add"
onClick={() => setIsMenuOpen(prev => !prev)}
aria-label="Add library"
type="button"
className={`hero-nav__item ${activePage === 'home' ? 'is-active' : ''}`}
onClick={() => setActivePage('home')}
>
+
Ana Sayfa
</button>
{isMenuOpen && (
<div className="inline-menu">
<button
type="button"
className="inline-menu__item"
onClick={() => {
setIsImportModalOpen(true);
closeMenus();
}}
>
Elle Ekle
</button>
<button
type="button"
className="inline-menu__item"
onClick={() => {
setIsUploadModalOpen(true);
closeMenus();
}}
>
Dosya Yükle
</button>
<button
type="button"
className={`hero-nav__item ${activePage === 'repo-scans' ? 'is-active' : ''}`}
onClick={() => setActivePage('repo-scans')}
>
Repo Taramaları
</button>
</nav>
</header>

{activePage === 'home' && (
<>
<section className="search-row">
<div className="search-bar">
<input
type="search"
placeholder="Search libraries or versions..."
value={query}
onChange={event => setQuery(event.target.value)}
/>
</div>
<div className="inline-add-wrapper" ref={menuRef}>
<button
className="inline-add"
onClick={() => setIsMenuOpen(prev => !prev)}
aria-label="Add library"
type="button"
className="inline-menu__item"
onClick={() => {
setIsRepoModalOpen(true);
closeMenus();
}}
>
Repo Linki
+
</button>
{isMenuOpen && (
<div className="inline-menu">
<button
type="button"
className="inline-menu__item"
onClick={() => {
setIsImportModalOpen(true);
closeMenus();
}}
>
Elle Ekle
</button>
<button
type="button"
className="inline-menu__item"
onClick={() => {
setIsUploadModalOpen(true);
closeMenus();
}}
>
Dosya Yükle
</button>
<button
type="button"
className="inline-menu__item"
onClick={() => {
setIsRepoModalOpen(true);
closeMenus();
}}
>
Repo Linki
</button>
</div>
)}
</div>
)}
</div>
</section>
<LibrarySummary libraries={filteredLibraries} />
</section>
<LibrarySummary libraries={filteredLibraries} />

<ImportModal isOpen={isImportModalOpen} onClose={() => setIsImportModalOpen(false)} onImported={loadLibraries} />
<UploadModal
isOpen={isUploadModalOpen}
onClose={() => {
setIsUploadModalOpen(false);
loadLibraries();
}}
onImported={loadLibraries}
/>
<RepoModal
isOpen={isRepoModalOpen}
onClose={() => setIsRepoModalOpen(false)}
onImported={loadLibraries}
/>

<ImportModal isOpen={isImportModalOpen} onClose={() => setIsImportModalOpen(false)} onImported={loadLibraries} />
<UploadModal
isOpen={isUploadModalOpen}
onClose={() => {
setIsUploadModalOpen(false);
loadLibraries();
}}
onImported={loadLibraries}
/>
<RepoModal
isOpen={isRepoModalOpen}
onClose={() => setIsRepoModalOpen(false)}
onImported={loadLibraries}
/>
{loading && <p>Loading libraries...</p>}
{error && <p className="error">{error}</p>}
</>
)}

{loading && <p>Loading libraries...</p>}
{error && <p className="error">{error}</p>}
{activePage === 'repo-scans' && (
<RepoScans />
)}

</div>
);
Expand Down
9 changes: 9 additions & 0 deletions apps/web/src/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,12 @@ export async function analyzeFileUpload(file) {
}
return res.json();
}

export function fetchRepositoryScans({ limit = 100, query } = {}) {
const params = new URLSearchParams();
if (limit) params.append('limit', String(limit));
if (query) params.append('q', query);
const searchPath = query ? '/repository-scans/search' : '/repository-scans';
const suffix = params.toString() ? `?${params.toString()}` : '';
return request(`${searchPath}${suffix}`);
}
147 changes: 147 additions & 0 deletions apps/web/src/components/RepoScans.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { useEffect, useState } from 'react';
import { fetchRepositoryScans } from '../api/client.js';

export default function RepoScans() {
const [repoScans, setRepoScans] = useState([]);
const [repoLoading, setRepoLoading] = useState(false);
const [repoError, setRepoError] = useState(null);
const [repoQuery, setRepoQuery] = useState('');
const [formatter] = useState(() => {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
return new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: tz
});
});

const loadRepoScans = async (queryText = '') => {
try {
setRepoLoading(true);
const data = await fetchRepositoryScans({ query: queryText || undefined });
setRepoScans(data || []);
setRepoError(null);
} catch (err) {
setRepoError(err.message);
} finally {
setRepoLoading(false);
}
};

useEffect(() => {
loadRepoScans(repoQuery);
}, [repoQuery]);

const handleSearch = (e) => {
e?.preventDefault();
loadRepoScans(repoQuery);
};

return (
<section className="panel repo-scans">
<div className="repo-scans__header">
<div>
<h2>Repository Taramaları</h2>
<p className="muted">
Repo linki ile yapılan taramaları buradan listeleyip, hangi repoda hangi kütüphane var takip edeceğiz.
Detaylar geldiğinde bu sayfayı genişleteceğiz.
</p>
</div>
</div>
<div className="repo-scans__table">
<form className="repo-scans__search" onSubmit={handleSearch}>
<input
type="search"
placeholder="Repo, platform, dosya yolu veya kütüphane adı/versiyon ara..."
value={repoQuery}
onChange={e => setRepoQuery(e.target.value)}
/>
<button type="submit" disabled={repoLoading}>
{repoLoading ? 'Aranıyor…' : 'Ara'}
</button>
</form>
{repoLoading && (
<div className="repo-scans__row repo-scans__row--empty">
<div className="repo-scans__empty">
<p>Yükleniyor...</p>
<p className="muted">Repo kayıtları getiriliyor.</p>
</div>
</div>
)}
{repoError && (
<div className="repo-scans__row repo-scans__row--empty">
<div className="repo-scans__empty">
<p>Bir hata oluştu.</p>
<p className="muted">{repoError}</p>
</div>
</div>
)}
{!repoLoading && !repoError && repoScans.length === 0 && (
<div className="repo-scans__row repo-scans__row--empty">
<div className="repo-scans__empty">
<p>Henüz kayıt yok.</p>
<p className="muted">İlk repo taraması geldiğinde burada görünecek.</p>
</div>
</div>
)}
{!repoLoading && !repoError && repoScans.length > 0 && (
<div className="repo-scans__list">
{repoScans.map(scan => {
const deps = scan.dependencies || [];
const repoLabel = scan.repository_name || scan.repository_url;
const lastUpdated = scan.updatedAt || scan.updated_at || scan.updated_at; // fallback just in case
return (
<div className="repo-scans__group" key={scan._id || scan.id || scan.repository_url}>
<div className="repo-scans__repo-heading">
<div className="repo-scans__repo-meta">
<span className="repo-scans__repo-title">
{scan.repository_url ? (
<a href={scan.repository_url} target="_blank" rel="noreferrer">{repoLabel}</a>
) : repoLabel}
</span>
{scan.repository_platform && <span className="repo-scans__platform">{scan.repository_platform}</span>}
</div>
{lastUpdated && (
<span className="repo-scans__date repo-scans__date--right">
Son tarama: {formatter.format(new Date(lastUpdated))}
</span>
)}
</div>
<div className="repo-scans__deps">
{deps.length === 0 && (
<div className="repo-scans__row repo-scans__row--empty">
<div className="repo-scans__empty">
<p>Dependency dosyası bulunamadı.</p>
</div>
</div>
)}
{deps.map((dep, idx) => (
<div className="repo-scans__dep" key={`${dep.library_path}-${idx}`}>
<div className="repo-scans__path">{dep.library_path}</div>
<ul className="repo-scans__libs">
{(dep.libraries || []).map((lib, lidx) => (
<li key={`${dep.library_path}-${lib.library_name}-${lidx}`}>
<span className="repo-scans__lib-name">{lib.library_name}</span>
<span className="repo-scans__lib-version">versiyon {lib.library_version || 'unknown'}</span>
</li>
))}
{(dep.libraries || []).length === 0 && (
<li className="muted">Kütüphane bulunamadı</li>
)}
</ul>
</div>
))}
</div>
</div>
);
})}
</div>
)}
</div>
</section>
);
}
Loading
Loading