Bygga
Type the product name from the box. Bygga shows every assembly step in 3D, with the parts, screws and tools for each one, and reads it out loud.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Showcasing all INA Gas stations, their options, Map how to find them and direct contact phone numbers. Search include options which aren't visible to all customers like AdBlue from dispenser but are visible via INA Loyalty application so admins can change that details. All data is pulled from ina.hr website.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A searchable list of all INA petrol stations in Croatia: working hours, which fuels each station sells (with prices), services, contact details and an "open now" bubble. Everything is pulled automatically from ina.hr/trazilica-benzinskih-postaja by a cron job.
idzanhr/plavi_diesel-php-cms-rebuild (same palette, cards,
security model, deploy), extended from 129 blue-diesel stations to the whole INA network and every fuel.<repo root> = web root
index.html (+ .gz) ← GENERATED: price overview + every station card pre-rendered
privatnost.html, kolacici.html, 404.html, data/stations.json ← GENERATED
assets/css/site.css ← light-dark() colour tokens (WCAG AA checked in both themes)
assets/js/theme-init.js ← applies a saved light/dark choice before the first paint
assets/js/site.js, hours.js ← theme toggle, search/filters/sorting, "Sada radi"
customer-gas-updater/ ← hidden admin: index.php (login → 2FA → editor), api.php, assets/
app/src/ ← InaSource (ina.hr parsers), InaSync, Stations, Render, Fuels, Auth, Totp…
app/templates/ ← page templates
sql/ bin/ deploy/ tests/
Fuels::CATEGORIES). Each shows
the lowest and highest price on INA stations at the last refresh from ina.hr (motorway / transit stations are
usually the highest), with the date of that refresh; one price when every station charges the same. Each station's
own price, with "nova cijena, prije …" for a week after a change, is on its card.adblue_have column). Whether AdBlue can be dispensed from a pump nozzle is not on ina.hr, only in the
INA Loyalty app (tap the station). The admin ticks "Moguće istakanje AdBlue iz pištolja" on the station. The card
then says "moguće istakanje iz pištolja", and the filter gets "AdBlue – istakanje iz pištolja". The sync never
changes this checkbox.?gorivo=lpg&zupanija=…&prikaz=sada), so a search can be shared.localStorage, applied before first paint); toggling back to what the system shows removes the override.bin/sync-ina.php)div#mdata[stations] is matched to a station by its ina.hr link.
New ones are added automatically. Name, phone and coordinates are updated. Address, city and postcode are only
filled while empty, so addresses checked by hand or imported stay as they are. A missing county
is taken from the finder or guessed from the postcode (10xxx → Grad Zagreb or Zagrebačka). Stations the finder no
longer lists are flagged, never deleted ("Više nisu u tražilici ina.hr" in the editor).price_history row and an
entry in the Dnevnik. A fuel listed without a price keeps its last known price; a page that cannot be read
changes nothing.'ina_auto_close' => false to only flag it). Other closure wording is only flagged.
Closures the admin sets are never touched.What you control in the editor (never overwritten by the sync): nicknames, a public note on the card, Ima AdBlue, AdBlue iz pištolja, county, address/city/postcode once set, temporary closures with dates, Sakrij s javne stranice, and Zaključaj ručne izmjene (the sync then keeps your name/address/phone/hours/services; fuels and prices still come from ina.hr). Prices cannot be entered by hand. Stations still listed on ina.hr can only be hidden, not deleted (the sync would add them back).
Check the parsers against the live site once after deploying. This code was written without access to ina.hr from the build machine: the hours table,
div#mdataand the "Goriva u ponudi" list follow the structure the previous project already parses successfully, while the finder's extra keys (name, address, coordinates…) and the services list are read defensively under several likely names. Runphp bin/ina-probe.php # finder: first raw entry + what was read php bin/ina-probe.php https://www.ina.hr/station/murter/ # one page: hours, fuels, services, detailsIf a field comes back empty everywhere, adjust the key list in
InaSource::parseDirectory()or the heading pattern inparseServices()– the tests intests/run.phpshow the expected shapes.
pdo_mysql, curl, dom, mbstring, intl recommended).git clone https://github.com/idzanhr/ina_mpm_statonfinder-gas_prices.git .
cp app/config.example.php app/config.php
php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;' # → app_key
nano app/config.php # dsn, user, pass, app_key
chmod 600 app/config.php
php bin/install.php # tables, admin, station list from ina.hr, public pages
php bin/sync-ina.php --all # hours, fuels, prices, services (several minutes)
https://lokator-benzinskih.crodesign93.eu/customer-gas-updater/, log in, scan the QR code, save the 8 recovery codes.php = the site's PHP version):
* 0,4 * * * php /path/to/bin/sync-ina.php # each minute of 00:xx and 04:xx: 1/60 of the stations → two full rounds a day
5 0 * * * php /path/to/bin/publish.php # closures that start/end today are right even without JS
Add more hours (e.g. * 0,4,12 * * *) for fresher prices. Requests are spaced by ina_delay_ms.Updating later: git pull && php bin/upgrade.php. Generated files are git-ignored, so pulls never conflict.
Uvoz / izvoz → Uvoz takes a CSV saved from Excel ("CSV UTF-8", ; or ,) with a header row. Any subset of the
export's columns works, e.g. name;address;city;county;adblue_have.
ina_url, then by name + city, then by name alone when exactly one station has it.
Case, spaces around "-" and č/ć/đ/š/ž don't matter, and the station keeps its own spelling of the name.Vukovarsko-srijemska).Visitors → Bunny → this server. deploy/nginx-vhost.conf is already set up for that
(domain lokator-benzinskih.crodesign93.eu):
X-Inapostaje-Origin: <secret>, which only Bunny adds.
Anything else (someone calling the server's IP directly) gets 403. Let's Encrypt renewals
(/.well-known/acme-challenge/) are the only exception.X-Forwarded-For entry, the one Bunny
adds. The admin lockout, the POST rate limit and the Dnevnik therefore see real people, not Bunny servers.
This is safe only because of the lock above. Keep 'trust_proxy' => false in app/config.php.data/stations.json are no-cache (Bunny revalidates, a cheap 304). CSS/JS have a content
hash in the URL (site.css?v=…) and are cached for a year. The admin is no-store.Do these once, in this order (setting names as in the Bunny dashboard):
openssl rand -hex 24 and keep the value.lokator-benzinskih.crodesign93.eu before DNS points to Bunny. Renewals keep working through Bunny later.https://<server IP> (the IP, not the domain, which would loop back through Bunny).lokator-benzinskih.crodesign93.eu.lokator-benzinskih.crodesign93.eu and enable its free SSL certificate.*. Action Add Request Header (the one marked
Origin; Set Response Header would send it to the visitor instead and the server would answer 403), name
X-Inapostaje-Origin, value = the secret from step 1.*/customer-gas-updater*. Action Bypass
Cache (or Override Cache Time 0 + Override Browser Cache Time 0).site.css?v=… is served stale.CHANGE-ME-BUNNY-SECRET with the secret from
step 1 and keep the regex around it: "~^<your secret>$" 1;. Then save. The regex form matters, because a long plain key
fails with "could not build map_hash". Never leave the placeholder: it is public in this repo.Emergency (Bunny down, the site must be reachable directly): in the vhost, put # in front of the five lines
under # --- BUNNY CDN (the if and the four real_ip lines) and save. Remove the # again afterwards.
One admin (admin.id pinned to 1, created only from the CLI) · bcrypt password (min. 12) · mandatory TOTP 2FA
(RFC 6238, AES-256-GCM encrypted secret, replay protection, 8 one-time recovery codes) · 3 failures per IP / 20
overall → 15 min lockout · __Host- cookie, HttpOnly, SameSite=Strict, 30 min idle / 8 h max · CSRF token +
Origin check · strict CSP without inline scripts · audit log (Dnevnik) of every change and every sync event.
| Command | |
|---|---|
php bin/sync-ina.php [--all | --directory | --minute=N | --station="Murter"] [--verbose] | sync with ina.hr |
php bin/ina-probe.php [url] | show what the parsers read from ina.hr (writes nothing) |
php bin/publish.php | regenerate all public files |
php bin/set-admin.php [--email=…] [--temporary] | change admin e-mail/password |
php bin/reset-2fa.php [--print] | lost phone and recovery codes |
php bin/upgrade.php | after git pull |
php bin/bunny-purge.php [--zone] | clear Bunny's cache |
cp app/config.example.php app/config.php # dsn: 'sqlite:' . __DIR__ . '/../var/ina-postaje.sqlite', app_key
mkdir -p var && php bin/install.php # add --no-sync without internet access
php -S 127.0.0.1:8000 -t . bin/dev-router.php # same whitelist as production; admin at /customer-gas-updater/
php tests/run.php # 46 tests: TOTP, crypto, ina.hr parsers, sync (add/flag/prices/closures/locks), rendering, import
node tests/hours.test.js # 18 tests: "Sada radi" – formats, overnight, non-stop, holidays
node tests/contrast.test.js # 44 checks: WCAG AA contrast of every token pair, light + dark
Co-Authored-By on its commitslokator-benzinskih*.b-cdn.netcurl -skI https://127.0.0.1/ -H 'Host: lokator-benzinskih.crodesign93.eu' | head -1 # on the server → 403
curl -skI https://127.0.0.1/ -H 'Host: lokator-benzinskih.crodesign93.eu' \
-H 'X-Inapostaje-Origin: CHANGE-ME-BUNNY-SECRET' | head -1 # → 403 (placeholder replaced)
curl -sI https://lokator-benzinskih.crodesign93.eu/ | grep -iE '^HTTP|cache-control' # → 200, no-cache
Log in to the admin and save something. Dnevnik must show your IP, not a Bunny one.app/config.php:
'bunny' => ['api_key' => '…', 'pullzone_id' => '<number from the pull zone URL>', 'site_url' => 'https://lokator-benzinskih.crodesign93.eu'].
Every save, sync and bin/publish.php then clears Bunny's copy. A failed purge never blocks a save; it is logged
in the Dnevnik. By hand: php bin/bunny-purge.php (--zone for the whole pull zone).