From 726785dfcccdb10a7d1e92f2b01d192eb8076244 Mon Sep 17 00:00:00 2001 From: Erol Date: Thu, 5 Mar 2026 09:25:15 +0100 Subject: [PATCH] =?UTF-8?q?Er=20skraping=20ferdig=20n=C3=A5=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/__pycache__/main.cpython-311.pyc | Bin 19570 -> 20371 bytes backend/main.py | 21 +- backend/scrape_status.py | 23 +- frontend/src/app/admin/page.tsx | 300 ++++++++++++++++++----- frontend/src/app/page.tsx | 7 +- 5 files changed, 283 insertions(+), 68 deletions(-) diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index 841e055af9c3a95d05a6c6fcf2253467bd23becb..c04ee5f4b6f7574067e9f434d3b64bcf518ff9af 100644 GIT binary patch delta 3273 zcma)8Yj9J?6~23=E6K9_if!-<``Q>=GL|e^et<0q#x|x7?ge zAxvyYvcCJ>v*)q*?C$Zm`sgANmq_6c3ks|fw6_YchPn4bVFhvhw0WA$%hHfUD;6bM z$!eAil5`Dvw)yR}YEkk#Xf>;2EtNU7G%C}YMWcT!v(ZgVhEeUJaS5!~?s@oIWr@kO zjRuPMJ(u0swlz`P z*R7<7_0pb6{a|<{B~*#{{607~-^7mG)-osUrM~MsK3Nt-qROz|9XpddjY-3SMhJLS zVv?Uw$uA$4n1LB7@qv~pAjv3E!?5HqYLE2Crg@wVCxVIT_~9uUOfXFjQ_U3DHIF$= z;)dE5jcF^0 z@p)05cfhyYXkU2j{N9VhFCR!5?eN?(+Lzlq7KY9pJx^2B&h!RXx}Ys(?11N%jv#Oc z&ebZW^TOS0veCb?1d>@QQM)n|PCZtba2odVXSSp&-rTH-&NK5O{Sb zyNli@q$6o6={JCWUs)IF5NFGJloMc$ByW^`#Xz1E?GMe>kr`pFuQlQH{G4#r*C?-mcr|&j{vW2=b09j1W(`KhVmzD}KN;uGpyOGD zS#i+rBB#aE_6zyC;%lf~6jhEI@aksso-oR7Nq zVB6(VGGPTLz0FBWCb$7iLhawsGe;)OIRiD{<+E*|&ChAM=l;=v^kv5xlvW!VDGT2jT^zRmeJK&rtqIVp>jyUwX9 zyGTa*Y`oHuk?Tb7jwj~LYH_AmDkx9e8kTKkXd}X)CR~EdgXCTft{R ztyEl*F4}UX;?G5%RFUT=c9YpVHh>d4Xb zkw7Z=R659RRgI>rMpI=Ee*RwDoa&fP9ixcZr*tM&I+N*s@S0cjy}14G9jX144ZHK7 z9x=Fcc)s_+bAO(FzH{f!=W&bU{db)VbQylU+tuGDzuRUV@G9?a?;LO`H#fKx7~J$4 zzCcf|Ldl!T2OUfkQ)tMS;NH zMyGegJAF;cdm!#ke%RM1lX}rMP<>o4b%k*r;}4;+`5^>A_f3Tkh! unu?Qu`N9q2Z6LeIl~6{)cWTF>tZ+ZB7$kW&Nt3>ej6RutM7+2E+W!G~iT(uu delta 2561 zcma)7S!^3c7~Wa$#z&l_5OO#PiR&hA?3~VhIL+0AyA2cpp~A8}lf;c{H?!-Al$Ia} zcPq7;hZb=a5N#1vQCL8z@BlANYT}AywQ`M!Vns=j{K_ z%>R#go&0``|Prnj=X)0HP=(7>A+qxG!*c!Rh}F7!#vF)q=nHwksq z_2UHJ)1DJ5Si_qcL;~wZv~H5wKdaaa527 z@k(wpsW;B${$R12)<`1BHKvI(1%i0R&^k@M3mgK**>ztNusxUSGTyw|Zvox8q7D!! z#Xai7U@>M47f)Hpi1^Ue)cOGaLyg1|8uQ6~7jS$OK*NP`ySX3LwpetGX?w$Qz8lzC zqok~rkln_e!BG5|dHpv+*fZ96^P3Z+Iq_2ve@1 z=pe!&@oZhYdJ@PljnC_Tvyi97d=oRunx}{p_s=Jra$_^!B8~?7$)DoeKx5@KLNwJe zrbi}V%MOYSEnf8uc>ZeK)AE+unZ!w~*Uc-Fb^1>h{vOz7!rCHgyQ*^D2mWu#xV!6p z;;;dYyYh!5>^+yQQl>eHo3@q|SgCEl^s+?luUe?%6JJ-jCqLX;f|?{Q&~*uxT3$N20N)K9i{=?B{N=z2DvuP})B%!`Zj7heQgW&Is{h{E9QV3ZzLZzAf(~E`EDc`ljSx$%9ec^vZo1M|6i}j*o@V?pq zo$xiLxO6F7m@UzoV1-YU;Ref@!I~kj{Ds#s>{q{NZXd2u7m90CU>5uqP%kt%hC8hb zJ!H7ks2uu*KyR^ao@x0A+xI* zwi-vbohPJEJaIeAW#}}7@P([]); const [loading, setLoading] = useState(true); const [selectedFacilities, setSelectedFacilities] = useState([]); - - // NYTT: Holder styr på om en skraping pågår akkurat nå const [isScraping, setIsScraping] = useState(false); - // Henter data fra databasen + // NY: State for å sjekke om sidebaren er klappet sammen + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); + + const [editingFacility, setEditingFacility] = useState(null); + + // OPPDATERT: Lagt til ai_instruction og courses for manuell overstyring + const [editForm, setEditForm] = useState({ + scrape_status_url: '', + scrape_status_selector: '', + scrape_method: '', + ai_instruction: '', + courses: [] as any[] + }); + + const [isSaving, setIsSaving] = useState(false); + const fetchFacilities = () => { fetch(`${API_URL}/facilities`) .then(res => res.json()) @@ -30,12 +43,10 @@ export default function AdminDashboard() { .catch(() => setLoading(false)); }; - // Hent data ved første innlasting useEffect(() => { fetchFacilities(); }, []); - // NYTT: Hvis skraping pågår, oppdater tabellen hvert 10. sekund! useEffect(() => { let interval: NodeJS.Timeout; if (isScraping) { @@ -62,8 +73,13 @@ export default function AdminDashboard() { } }; - // NYTT: Sender IDene til API-et og starter auto-oppdatering - const handleRunScrapers = async () => { +const handleRunScrapers = async () => { + // Hvis den allerede skraper, lar vi brukeren trykke på knappen for å avbryte UI-oppdateringen + if (isScraping) { + setIsScraping(false); + return; + } + setIsScraping(true); try { const response = await fetch(`${API_URL}/admin/run-scraper`, { @@ -71,15 +87,13 @@ export default function AdminDashboard() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ facility_ids: selectedFacilities }) }); - if (!response.ok) throw new Error("Kunne ikke starte skraping"); - // Valgfritt: Fjern avhukingene når jobben har startet - setSelectedFacilities([]); + // Beregn dynamisk timeout: 40 sekunder per valgte anlegg (Minimum 1 minutt) + const timeoutMs = Math.max(selectedFacilities.length * 40 * 1000, 60000); - // Stopper auto-oppdateringen etter 10 minutter (for sikkerhets skyld) - setTimeout(() => setIsScraping(false), 10 * 60 * 1000); - + setSelectedFacilities([]); + setTimeout(() => setIsScraping(false), timeoutMs); } catch (error) { console.error(error); alert("Feil ved start av skraperen."); @@ -87,49 +101,216 @@ export default function AdminDashboard() { } }; + // OPPDATERT: Laster inn eksisterende banestatuser og ai_instruction + const openEditModal = (facility: any) => { + setEditingFacility(facility); + setEditForm({ + scrape_status_url: facility.scrape_status_url || '', + scrape_status_selector: facility.scrape_status_selector || '', + scrape_method: facility.scrape_method || 'css_selector', + ai_instruction: facility.ai_instruction || '', + courses: facility.course_statuses ? facility.course_statuses.map((c: any) => ({id: c.id, name: c.name, status: c.status})) : [] + }); + }; + + const handleSaveEdit = async () => { + setIsSaving(true); + try { + const response = await fetch(`${API_URL}/admin/facilities/${editingFacility.id}/scrape-settings`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(editForm) + }); + if (!response.ok) throw new Error("Feil ved lagring"); + + setEditingFacility(null); + fetchFacilities(); + } catch (error) { + alert("Kunne ikke lagre endringene."); + console.error(error); + } finally { + setIsSaving(false); + } + }; + if (loading) return
LASTER DASHBORD...
; return ( -
-