Compare commits
34 Commits
d73e48351f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99f7d8f0bb | ||
|
|
85a0a54eb4 | ||
|
|
5c1d3c7ef9 | ||
|
|
da14d0d05d | ||
|
|
5b521661ec | ||
|
|
3469a09a9f | ||
|
|
5d5e12d7e7 | ||
|
|
f5af9e46e1 | ||
|
|
bb7007aa88 | ||
|
|
900ffe0f06 | ||
|
|
bef271c1a2 | ||
|
|
2996812209 | ||
|
|
d5d4574683 | ||
|
|
d93191f675 | ||
|
|
1e4fa53fb2 | ||
|
|
8346a731a6 | ||
|
|
72ace6ff50 | ||
|
|
535aefc0f3 | ||
|
|
15b4b40681 | ||
|
|
4692e89736 | ||
|
|
739e081296 | ||
|
|
5abe1edb71 | ||
|
|
31b0619c90 | ||
|
|
ea36f28c17 | ||
|
|
17f4eb752b | ||
|
|
eb6557eeb3 | ||
|
|
74db1dad77 | ||
|
|
9653fcbaf9 | ||
|
|
d8ff358dd7 | ||
|
|
d7aa7c2013 | ||
|
|
4820289252 | ||
|
|
b7931731f9 | ||
|
|
0bd480d103 | ||
|
|
21a538cbb8 |
@@ -1,97 +0,0 @@
|
||||
name: Build & Deploy to EasyPanel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --no-audit --no-fund
|
||||
|
||||
- name: Build Astro site
|
||||
run: npm run build
|
||||
|
||||
- name: Trigger EasyPanel redeploy
|
||||
if: success()
|
||||
env:
|
||||
EASYPANEL_TOKEN: ${{ secrets.EASYPANEL_TOKEN }}
|
||||
EASYPANEL_PROJECT_NAME: ${{ secrets.EASYPANEL_PROJECT_NAME }}
|
||||
EASYPANEL_SERVICE_NAME: ${{ secrets.EASYPANEL_SERVICE_NAME }}
|
||||
run: |
|
||||
# EasyPanel tRPC endpoint for app service redeploy
|
||||
EASYPANEL_API="https://panelwebsite.moreminimore.com/api"
|
||||
DEPLOY_URL="${EASYPANEL_API}/trpc/services.app.deployService"
|
||||
|
||||
# Guard: required secrets
|
||||
if [ -z "$EASYPANEL_TOKEN" ] || [ -z "$EASYPANEL_PROJECT_NAME" ] || [ -z "$EASYPANEL_SERVICE_NAME" ]; then
|
||||
echo "::warning::One or more required secrets are empty:"
|
||||
[ -z "$EASYPANEL_TOKEN" ] && echo " - EASYPANEL_TOKEN"
|
||||
[ -z "$EASYPANEL_PROJECT_NAME" ] && echo " - EASYPANEL_PROJECT_NAME"
|
||||
[ -z "$EASYPANEL_SERVICE_NAME" ] && echo " - EASYPANEL_SERVICE_NAME"
|
||||
echo "Skipping deploy trigger. Set these in repo settings to enable auto-deploy."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# tRPC mutation payload: {"json":{"projectName":"...","serviceName":"..."}}
|
||||
PAYLOAD=$(jq -nc \
|
||||
--arg pj "$EASYPANEL_PROJECT_NAME" \
|
||||
--arg sv "$EASYPANEL_SERVICE_NAME" \
|
||||
'{json:{projectName:$pj, serviceName:$sv}}')
|
||||
|
||||
echo "Triggering EasyPanel redeploy"
|
||||
echo " Endpoint: $DEPLOY_URL"
|
||||
echo " Project: $EASYPANEL_PROJECT_NAME"
|
||||
echo " Service: $EASYPANEL_SERVICE_NAME"
|
||||
echo " Payload: $PAYLOAD"
|
||||
echo ""
|
||||
|
||||
HTTP_CODE=$(curl -sS -o /tmp/easypanel-response.json -w "%{http_code}" \
|
||||
-X POST "$DEPLOY_URL" \
|
||||
-H "Authorization: Bearer *** \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD")
|
||||
|
||||
RESPONSE=$(cat /tmp/easypanel-response.json)
|
||||
echo " HTTP $HTTP_CODE"
|
||||
echo " Response: $RESPONSE"
|
||||
|
||||
# 2xx = success
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo ""
|
||||
echo "EasyPanel redeploy triggered successfully"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 4xx with explicit field errors = workflow is right, payload is wrong
|
||||
if [ "$HTTP_CODE" -ge 400 ] && [ "$HTTP_CODE" -lt 500 ]; then
|
||||
echo "::error::EasyPanel rejected the deploy request (HTTP $HTTP_CODE). See response above."
|
||||
echo "::error::Likely the payload shape does not match what the panel expects."
|
||||
echo "::error::Check https://panelwebsite.moreminimore.com/api for the procedure spec."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5xx = server error
|
||||
echo "::error::EasyPanel API error (HTTP $HTTP_CODE). Response: $RESPONSE"
|
||||
exit 1
|
||||
|
||||
- name: Upload build artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Lint & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
- run: npm ci --no-audit --no-fund
|
||||
- name: Build (catches syntax + missing-image errors before deploy)
|
||||
run: npm run build
|
||||
@@ -1,7 +1,7 @@
|
||||
# =====================================================================
|
||||
# Stage 1: Build the Astro static site
|
||||
# =====================================================================
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@astrojs/react'
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://dealplustech.com',
|
||||
site: 'https://dealplustech.co.th',
|
||||
output: 'static',
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
@@ -22,6 +23,7 @@ export default defineConfig({
|
||||
},
|
||||
integrations: [
|
||||
react(),
|
||||
sitemap(),
|
||||
],
|
||||
build: {
|
||||
assets: '_assets',
|
||||
|
||||
146
docs/ci-setup.md
@@ -1,85 +1,109 @@
|
||||
# CI/CD Setup — EasyPanel Deploy
|
||||
# CI/CD Setup — Deploy via Gitea Webhook
|
||||
|
||||
Push to `main` triggers `build-and-deploy.yml`:
|
||||
1. Builds the Astro static site into `dist/`
|
||||
2. Uploads `dist/` as a 7-day artifact
|
||||
3. Calls EasyPanel tRPC endpoint to trigger a redeploy
|
||||
The site auto-rebuilds on every push to `main` via a Gitea **webhook**
|
||||
(no Actions runner, no `.gitea/workflows/`, no act_runner required).
|
||||
|
||||
## Required Gitea repo secrets
|
||||
## How it works
|
||||
|
||||
Go to **Settings → Actions → Secrets** and add three secrets:
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
| Name | Value (for this site) | Where to get it |
|
||||
|---|---|---|
|
||||
| `EASYPANEL_TOKEN` | `cmq61xwrv000407qn9e2hhfuw` | EasyPanel → profile/settings → API tokens |
|
||||
| `EASYPANEL_PROJECT_NAME` | `customerwebsite` | EasyPanel → project name in the dashboard |
|
||||
| `EASYPANEL_SERVICE_NAME` | `dealplustech-astro` | EasyPanel → service name inside the project |
|
||||
## One-time webhook setup
|
||||
|
||||
> ⚠️ Project name ≠ repo name. The site lives in the `customerwebsite`
|
||||
> project on the panel; the repo is `dealplustech-astroreal`.
|
||||
In `https://git.moreminimore.com/kunthawat/dealplustech-astroreal/settings/hooks`:
|
||||
|
||||
If any of these are empty, the workflow logs a warning and skips the deploy
|
||||
trigger. The build still runs and the artifact is still uploaded.
|
||||
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 must be:
|
||||
The service on the panel side (`project=customerwebsite`,
|
||||
`service=dealplustech-astro`) must be:
|
||||
|
||||
- **Type: `app`**
|
||||
- **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`)
|
||||
on branch **`main`** (not `source-code`).
|
||||
- **Build type: `dockerfile`**, **file: `Dockerfile`** at the repo root.
|
||||
> The workflow used to trigger nixpacks builds, but nixpacks expects a
|
||||
> `start` script in `package.json` and an Astro static site doesn't
|
||||
> have one. Switching to a Dockerfile + nginx fixes that.
|
||||
- **Source: Git**, branch `main`
|
||||
- **Build type: `dockerfile`**, file `Dockerfile` at repo root
|
||||
- **Port: `80`**
|
||||
|
||||
If you need a different service type later, swap the endpoint in
|
||||
`.gitea/workflows/build-and-deploy.yml` to the matching procedure:
|
||||
- `services.app.deployService` (Dockerfile / app)
|
||||
- `services.box.rebuildDockerImage` (low-level)
|
||||
- `services.compose.deployService` (docker-compose)
|
||||
The Dockerfile is two-stage:
|
||||
|
||||
## Why Dockerfile and not nixpacks
|
||||
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)
|
||||
|
||||
Astro builds to static files in `dist/` — there is no Node server to run.
|
||||
Nixpacks tries to find a `start` command and fails. The Dockerfile in
|
||||
this repo:
|
||||
## Why not Gitea Actions?
|
||||
|
||||
1. **Stage 1** (`node:20-alpine`): runs `npm ci` then `npm run build` to
|
||||
produce `dist/`.
|
||||
2. **Stage 2** (`nginx:1.27-alpine`): copies `dist/` to nginx's web root
|
||||
and serves it.
|
||||
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"**.
|
||||
|
||||
`nginx.conf` ships gzip, security headers, 1-year cache for hashed
|
||||
assets, and a `try_files` fallback for client-side routes (Astro's
|
||||
file-based routing produces UTF-8 slugs).
|
||||
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.
|
||||
|
||||
## Verifying the trigger payload
|
||||
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.
|
||||
|
||||
If the deploy runs but the panel rejects it, the response in the workflow
|
||||
log will show the exact `zodErrors` field telling you which field name
|
||||
or shape is wrong. Update the `PAYLOAD` JSON in the workflow accordingly.
|
||||
## Troubleshooting
|
||||
|
||||
To test the payload shape from your local machine:
|
||||
### 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 -sS -X POST \
|
||||
"https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \
|
||||
-H "Authorization: Bearer *** -H "Content-Type: application/json" \
|
||||
-d '{"json":{"projectName":"customerwebsite","serviceName":"dealplustech-astro"}}'
|
||||
curl -X POST \
|
||||
"http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2"
|
||||
```
|
||||
|
||||
A 2xx response = the panel accepted the trigger. The service will start
|
||||
rebuilding/redploying in the EasyPanel dashboard.
|
||||
|
||||
## Common failure: "Failed to sync changes" (HTTP 500)
|
||||
|
||||
This is a **server-side** error, not a payload problem. It usually means:
|
||||
|
||||
- The EasyPanel service is currently in a state that can't be redeployed
|
||||
(e.g. the previous container is stuck, or a build is in progress).
|
||||
- The Docker daemon on the panel host is busy.
|
||||
|
||||
Try again in a few seconds. If it persists, open the EasyPanel dashboard
|
||||
and check the service's container state.
|
||||
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.
|
||||
|
||||
77
package-lock.json
generated
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "dealplustech-emdash",
|
||||
"name": "dealplustech-astroreal",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dealplustech-emdash",
|
||||
"name": "dealplustech-astroreal",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/react": "^5.0.5",
|
||||
"@astrojs/sitemap": "^3.7.3",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"astro": "^6.1.7",
|
||||
@@ -160,6 +161,17 @@
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/sitemap": {
|
||||
"version": "3.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz",
|
||||
"integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sitemap": "^9.0.0",
|
||||
"stream-replace-string": "^2.0.0",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/telemetry": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz",
|
||||
@@ -2329,8 +2341,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
|
||||
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
@@ -2355,6 +2365,15 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sax": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
|
||||
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
@@ -2562,6 +2581,12 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -5748,6 +5773,40 @@
|
||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sitemap": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
|
||||
"integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^24.9.2",
|
||||
"@types/sax": "^1.2.1",
|
||||
"arg": "^5.0.0",
|
||||
"sax": "^1.4.1"
|
||||
},
|
||||
"bin": {
|
||||
"sitemap": "dist/esm/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.5",
|
||||
"npm": ">=10.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sitemap/node_modules/@types/node": {
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sitemap/node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/smol-toml": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
|
||||
@@ -5779,6 +5838,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-replace-string": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
|
||||
"integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
@@ -5980,9 +6045,7 @@
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "11.0.5",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/react": "^5.0.5",
|
||||
"@astrojs/sitemap": "^3.7.3",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"astro": "^6.1.7",
|
||||
|
||||
BIN
public/images/aeroflex/gallery-1.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/images/aeroflex/gallery-10.jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
public/images/aeroflex/gallery-11.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/images/aeroflex/gallery-12.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/images/aeroflex/gallery-13.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/aeroflex/gallery-14.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/images/aeroflex/gallery-2.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/images/aeroflex/gallery-3.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/images/aeroflex/gallery-4.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/images/aeroflex/gallery-5.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
public/images/aeroflex/gallery-6.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/aeroflex/gallery-8.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/aeroflex/gallery-9.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
public/images/armflex/gallery-1.jpg
Normal file
|
After Width: | Height: | Size: 476 KiB |
BIN
public/images/armflex/gallery-2.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/images/armflex/gallery-3.jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
public/images/armflex/gallery-4.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
public/images/armflex/gallery-5.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/images/grilles/supply-air.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
public/images/maxflex/gallery-1.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/images/maxflex/gallery-3.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/images/maxflex/gallery-4.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/images/maxflex/gallery-5.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/images/maxflex/gallery-6.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/images/maxflex/gallery-7.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/images/maxflex/gallery-8.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/images/maxflex/gallery-9.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/images/microfiber/gallery-1.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/images/microfiber/gallery-10.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/images/microfiber/gallery-2.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/microfiber/gallery-3.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/microfiber/gallery-4.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/images/microfiber/gallery-5.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/gallery-6.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/images/microfiber/gallery-7.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/images/microfiber/gallery-8.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/images/microfiber/hero_1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/images/microfiber/microfiber-enf.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/images/microfiber/microfiber-fl.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-fld.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/images/microfiber/microfiber-fls.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-fr.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-frd.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-frk.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-fsf.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/images/microfiber/microfiber-glc.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/images/microfiber/microfiber-gts.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/microfiber/microfiber-gwpf.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/images/microfiber/microfiber-hi-temp.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/microfiber/microfiber-pfl.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/images/microfiber/microfiber-pipe-cover.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/microfiber-pln.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/images/microfiber/prod_0.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/prod_0_0.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/images/microfiber/prod_0_1.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/images/microfiber/prod_0_2.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_0_3.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_0_4.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/images/microfiber/prod_1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_10.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_11.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/prod_1_0.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/images/microfiber/prod_1_1.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/images/microfiber/prod_1_2.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_1_3.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_1_4.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/images/microfiber/prod_2.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/prod_2_0.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/images/microfiber/prod_2_1.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/images/microfiber/prod_2_2.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_2_3.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/microfiber/prod_2_4.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/images/microfiber/prod_3.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/microfiber/prod_4.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_5.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_6.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_7.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_8.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/microfiber/prod_9.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 472 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 101 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="#65a30d"/><text x="200" y="150" text-anchor="middle" fill="white" font-family="sans-serif" font-size="18">Coupling 05</text></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 98 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="#65a30d"/><text x="200" y="150" text-anchor="middle" fill="white" font-family="sans-serif" font-size="18">Coupling 07</text></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 581 KiB |
|
Before Width: | Height: | Size: 566 KiB |