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).