Compare commits
23 Commits
c8cf03a725
...
source-cod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7aa7c2013 | ||
|
|
4820289252 | ||
|
|
b7931731f9 | ||
|
|
0bd480d103 | ||
|
|
21a538cbb8 | ||
|
|
d73e48351f | ||
|
|
3efaf4d661 | ||
|
|
02c7b7b29b | ||
|
|
fe442790ab | ||
|
|
bd67810a3f | ||
|
|
b34f8fc2fb | ||
|
|
7c905bdb00 | ||
|
|
e1e285363f | ||
|
|
9ed0abb5d4 | ||
|
|
ff4e5ae346 | ||
|
|
52761c3946 | ||
|
|
4abb361897 | ||
|
|
47f28cdf65 | ||
|
|
5f4e667c65 | ||
|
|
238e6500a6 | ||
|
|
be565e416c | ||
|
|
63a58727f9 | ||
|
|
ef4b0f2e89 |
34
.dockerignore
Normal file
@@ -0,0 +1,34 @@
|
||||
# Build artifacts
|
||||
node_modules/
|
||||
dist/
|
||||
.astro/
|
||||
|
||||
# CI / Gitea
|
||||
.gitea/
|
||||
.github/
|
||||
|
||||
# Local dev / IDE
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
*.log
|
||||
*.tmp
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# Docker (don't include Docker files in the build context of themselves)
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose*.yml
|
||||
|
||||
# Docs
|
||||
docs/
|
||||
|
||||
# Misc
|
||||
coverage/
|
||||
*.tsbuildinfo
|
||||
2
.gitignore
vendored
@@ -25,3 +25,5 @@ uploads/
|
||||
|
||||
# Generated
|
||||
emdash-env.d.ts
|
||||
.hermes/
|
||||
scripts/__pycache__/
|
||||
|
||||
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# =====================================================================
|
||||
# Stage 1: Build the Astro static site
|
||||
# =====================================================================
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install deps with cache layer
|
||||
COPY package*.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
|
||||
# Build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# =====================================================================
|
||||
# Stage 2: Serve with nginx
|
||||
# =====================================================================
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
# Astro outputs to ./dist by default
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# nginx config: SPA-friendly, gzip, cache headers for static assets
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,10 +1,6 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import node from '@astrojs/node'
|
||||
import react from '@astrojs/react'
|
||||
import emdash, { local } from 'emdash/astro'
|
||||
import { sqlite } from 'emdash/db'
|
||||
import { google } from 'emdash/auth/providers/google'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
|
||||
@@ -12,10 +8,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://dealplustech.com',
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
output: 'static',
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
resolve: {
|
||||
@@ -29,14 +22,6 @@ export default defineConfig({
|
||||
},
|
||||
integrations: [
|
||||
react(),
|
||||
emdash({
|
||||
database: sqlite({ url: 'file:./data.db' }),
|
||||
storage: local({
|
||||
directory: './uploads',
|
||||
baseUrl: '/_emdash/api/media/file',
|
||||
}),
|
||||
authProviders: [google()],
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
assets: '_assets',
|
||||
|
||||
109
docs/ci-setup.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# CI/CD Setup — Deploy via Gitea Webhook
|
||||
|
||||
The site auto-rebuilds on every push to `main` via a Gitea **webhook**
|
||||
(no Actions runner, no `.gitea/workflows/`, no act_runner required).
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
git push origin main
|
||||
│
|
||||
▼
|
||||
Gitea Webhook (Gitea built-in, not Gitea Actions)
|
||||
│ POST Content-Type: application/json
|
||||
▼
|
||||
http://110.164.146.47:3000/api/deploy/<token>
|
||||
│ HTTP 200 "Deploying..."
|
||||
▼
|
||||
EasyPanel pulls repo, builds with Dockerfile, redeploys
|
||||
```
|
||||
|
||||
## One-time webhook setup
|
||||
|
||||
In `https://git.moreminimore.com/kunthawat/dealplustech-astroreal/settings/hooks`:
|
||||
|
||||
1. Click **Add Webhook → Gitea**
|
||||
2. Fill in:
|
||||
- **Payload URL**: `http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2`
|
||||
- **HTTP Method**: `POST`
|
||||
- **Content Type**: `application/json`
|
||||
- **Events**: **Push events**
|
||||
- **Active**: ✓
|
||||
- **Branch filter**: leave empty (or `main` to restrict)
|
||||
3. Click **Add Webhook**
|
||||
4. Test: click the webhook row → **Test Delivery** → **Push events** → confirm
|
||||
**Last Response** is HTTP 200.
|
||||
|
||||
Done. From now on every push to main redeploys automatically.
|
||||
|
||||
## EasyPanel service requirements
|
||||
|
||||
The service on the panel side (`project=customerwebsite`,
|
||||
`service=dealplustech-astro`) must be:
|
||||
|
||||
- **Type: `app`**
|
||||
- **Source: Git**, branch `main`
|
||||
- **Build type: `dockerfile`**, file `Dockerfile` at repo root
|
||||
- **Port: `80`**
|
||||
|
||||
The Dockerfile is two-stage:
|
||||
|
||||
1. `node:22-alpine` — runs `npm ci` and `npm run build` to produce
|
||||
`dist/`
|
||||
2. `nginx:1.27-alpine` — copies `dist/` to nginx's web root and serves
|
||||
it (with gzip, security headers, 1-year cache for hashed assets,
|
||||
and a `try_files` fallback for Astro's UTF-8 slugs)
|
||||
|
||||
## Why not Gitea Actions?
|
||||
|
||||
Gitea Actions does **not** ship with managed runners (unlike GitHub
|
||||
Actions' `ubuntu-latest`). Without a self-hosted `act_runner` registered
|
||||
with matching labels, any workflow run will fail with
|
||||
**"No matching online runner with label: ubuntu-latest"**.
|
||||
|
||||
The Gitea Webhook mechanism is built into Gitea itself — no runner
|
||||
required. It fires the same events but POSTs to a URL you control,
|
||||
which is the simplest possible deploy trigger.
|
||||
|
||||
If a real CI step is ever needed (lint, test, build artifact), install
|
||||
`act_runner` on a box, register it, and write a workflow using
|
||||
`runs-on: self-hosted`. See `act_runner` docs for setup.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Push happened, but site didn't update
|
||||
|
||||
1. Open the webhook row in Settings → Webhooks
|
||||
2. Open **Recent Deliveries**
|
||||
3. Check the most recent one:
|
||||
- Status 200 → deploy endpoint received it, check EasyPanel logs
|
||||
- Status != 200 → check the response body
|
||||
- **No recent delivery** → webhook is not bound to this branch/event
|
||||
or the push target branch doesn't match the filter
|
||||
|
||||
### Build fails inside EasyPanel
|
||||
|
||||
1. EasyPanel UI → service `dealplustech-astro` → Logs tab
|
||||
2. Look for the `nixpacks` or `docker build` step
|
||||
3. Common failures:
|
||||
- **"No start command could be found"**: you're on `nixpacks`. Switch
|
||||
build type to `dockerfile` and point at `Dockerfile` at repo root.
|
||||
- **"Node.js v20.x is not supported by Astro"**: the Dockerfile is
|
||||
using `node:20-alpine`. It should be `node:22-alpine` (Astro 6
|
||||
requires `>=22.12.0`).
|
||||
- **"Failed to sync changes"** (HTTP 500 from the deploy endpoint):
|
||||
usually a panel-side state issue. Retry, or open the service in
|
||||
the panel UI and check container state.
|
||||
|
||||
### Want to redeploy without a code change
|
||||
|
||||
The webhook URL itself is idempotent. You can hit it directly with:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
"http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2"
|
||||
```
|
||||
|
||||
A `200` response with body `Deploying...` means the panel accepted the
|
||||
trigger. The actual rebuild happens in the background — check EasyPanel
|
||||
UI for the progress.
|
||||
55
nginx.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# gzip
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/javascript
|
||||
text/xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/xml
|
||||
application/xml+rss
|
||||
image/svg+xml
|
||||
font/ttf
|
||||
font/otf;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Cache static assets aggressively (Astro hashes filenames)
|
||||
location ~* \.(?:js|css|woff2?|ttf|otf|eot|svg|jpg|jpeg|png|webp|avif|ico)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Don't cache HTML
|
||||
location ~* \.html$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
# Thai URL slugs (Astro file-based routing produces paths with UTF-8 chars)
|
||||
location / {
|
||||
try_files $uri $uri/ $uri.html /index.html;
|
||||
}
|
||||
|
||||
# healthcheck
|
||||
location /healthz {
|
||||
access_log off;
|
||||
return 200 "ok\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
4480
package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "dealplustech-emdash",
|
||||
"name": "dealplustech-astroreal",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"description": "Deal Plus Tech - ระบบน้ำคุณภาพสูง ราคาโรงงาน",
|
||||
@@ -7,17 +7,14 @@
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"start": "node ./dist/server/entry.mjs"
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/node": "^10.1.1",
|
||||
"@astrojs/react": "^5.0.5",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"astro": "^6.1.7",
|
||||
"emdash": "^0.14.0",
|
||||
"marked": "^18.0.3",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
|
||||
BIN
public/documents/16 Price List Armaflex 2567.pdf
Normal file
BIN
public/documents/19 Pricelist Aeroflex (update 2565).pdf
Normal file
BIN
public/documents/2026-New Update Thermobreak Price List.pdf
Normal file
BIN
public/documents/PRICE-LIST_TPPR_V28-2023 [26012023].pdf
Normal file
BIN
public/documents/Price List HDPE TAP.pdf
Normal file
BIN
public/documents/Price List MECH_V13-2021 [260864](1).pdf
Normal file
BIN
public/documents/เอสซีเจ ปี68.pdf
Normal file
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400">
|
||||
<rect width="400" height="400" fill="#2563eb"/>
|
||||
<text x="200" y="100" text-anchor="middle" fill="white" font-family="sans-serif" font-size="24" font-weight="600">เครื่องเชื่อม HDPE</text>
|
||||
<rect x="100" y="160" width="200" height="140" rx="15" fill="none" stroke="white" stroke-width="6"/>
|
||||
<line x1="200" y1="200" x2="200" y2="260" stroke="white" stroke-width="6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 517 B |
BIN
public/images/ball-jet/ball-jet-front.jpg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
public/images/ball-jet/ball-jet-main-hd.jpg
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
public/images/ball-jet/ball-jet-main.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/ball-jet/ball-jet-performance.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
public/images/ball-jet/ball-jet-specs.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/images/blog/10-things-checklist-hero.jpg
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
public/images/blog/grilles-eyeball.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/blog/grilles-fresh-air-types.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
public/images/blog/grilles-hing-type.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/images/blog/grilles-industrial.jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
public/images/blog/grilles-installation.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
public/images/blog/grilles-linear-slot.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/images/blog/grilles-plastic-content.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
public/images/blog/ppr-pipe-hero.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
public/images/blog/ppr-pipes-green.jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
public/images/blog/ppr-vs-hdpe-vs-upvc-hero.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
public/images/blog/ppr-welding-machine.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/blog/source/hdpe-cleaned.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
public/images/blog/source/hdpe-source.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
public/images/blog/source/ppr-cleaned.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
public/images/blog/source/ppr-thaippr-source.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
public/images/blog/source/ppr-tight.png
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
public/images/blog/source/ppr-transparent.png
Normal file
|
After Width: | Height: | Size: 864 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 715 KiB |
BIN
public/images/blog/source/upvc-cleaned.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/images/blog/thermobreak-cross-section.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/images/blog/thermobreak-foil-detail.jpg
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
public/images/blog/thermobreak-hero.jpg
Normal file
|
After Width: | Height: | Size: 619 KiB |
BIN
public/images/blog/thermobreak-installation.jpg
Normal file
|
After Width: | Height: | Size: 790 KiB |
BIN
public/images/blog/thermobreak-series-hero.jpg
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
public/images/blog/thermobreak-structure.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
public/images/blog/upvc-pipe-clean.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 217 KiB |
|
Before Width: | Height: | Size: 221 KiB |