Restructure project and update Helm chart for microservices
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Directory Changes: - Moved old unused files to 'old/' directory: - server.js, package.json, package-lock.json, node_modules - data/, public/, run_ora_map.sh, README copy.md - Clean root structure with only backend/ and frontend/ dirs - Updated .gitignore to ignore old directories Helm Chart Updates (oramap/): - Updated Chart.yaml to v0.2.0 with proper description - Split values.yaml for separate backend and frontend configurations - Created separate deployments for backend and frontend - Created separate services for backend (port 3000) and frontend (port 80) - Added ConfigMap for nginx configuration in Kubernetes - Added volume mounts for nginx config in frontend deployment - Updated ingress to route to frontend service - Added proper health checks for both services - Added resource limits and requests Frontend Updates: - Enhanced nginx.conf with dynamic backend routing - Supports both Docker Compose and Kubernetes service discovery This enables proper microservices deployment in Kubernetes with: - Separate scaling for frontend and backend - Independent deployments and rollbacks - Proper service discovery and load balancing
This commit is contained in:
parent
fccd8a0166
commit
c457db534b
16
.gitignore
vendored
16
.gitignore
vendored
@ -1,3 +1,19 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
nul
|
||||||
|
|
||||||
|
# Old files
|
||||||
|
old/
|
||||||
|
old-project/
|
||||||
|
oramap-chart/
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
# How to Run
|
|
||||||
```
|
|
||||||
npm init -y
|
|
||||||
npm install express
|
|
||||||
node server.js
|
|
||||||
```
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
[
|
|
||||||
{ "family": "Kafe (קאפח)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
|
|
||||||
{ "family": "Shiheb (שחב-שבח)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
|
|
||||||
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
|
|
||||||
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Manakhah (מנאכה)", "lat": 15.3019, "lng": 43.5983 },
|
|
||||||
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Dhamar (ד'מאר)", "lat": 14.5424, "lng": 44.4056 },
|
|
||||||
{ "family": "Salumi (סלומי-שלומי)", "city": "Al Kafla (אל קפלה)", "lat": 16.0240, "lng": 43.9790 },
|
|
||||||
{ "family": "Afgin (עפג'ין)", "city": "Sa'dah (צעדה)", "lat": 16.9402, "lng": 43.7639 },
|
|
||||||
{ "family": "Eraki (עראקי)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 }
|
|
||||||
]
|
|
||||||
@ -13,9 +13,19 @@ server {
|
|||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# API proxy to backend
|
# API proxy to backend service
|
||||||
|
# For Kubernetes: set BACKEND_HOST env var or use service name
|
||||||
|
# For Docker Compose: backend service name is 'backend'
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend:3000;
|
# In Kubernetes, this will be: oramap-backend:3000
|
||||||
|
# In Docker Compose, this will be: backend:3000
|
||||||
|
# Default to backend:3000
|
||||||
|
set $backend_host backend;
|
||||||
|
if ($http_x_backend_host != "") {
|
||||||
|
set $backend_host $http_x_backend_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_pass http://$backend_host:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
|
|||||||
@ -1,24 +1,11 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: oramap
|
name: oramap
|
||||||
description: A Helm chart for Kubernetes
|
description: Ora Map - Family Location Mapping Application (Microservices Architecture)
|
||||||
|
|
||||||
# A chart can be either an 'application' or a 'library' chart.
|
|
||||||
#
|
|
||||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
|
||||||
# to be deployed.
|
|
||||||
#
|
|
||||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
|
||||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
|
||||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# Chart version
|
||||||
# to the chart and its templates, including the app version.
|
version: 0.2.0
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
|
||||||
version: 0.1.0
|
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# Application version
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
appVersion: "1.0.0"
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
|
||||||
# It is recommended to use it with quotes.
|
|
||||||
appVersion: "1.16.0"
|
|
||||||
|
|||||||
45
oramap/templates/configmap.yaml
Normal file
45
oramap/templates/configmap.yaml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "oramap.fullname" . }}-nginx-config
|
||||||
|
labels:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
chart: {{ include "oramap.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# Main location
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API proxy to backend service in Kubernetes
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://{{ include "oramap.fullname" . }}-backend:{{ .Values.service.backend.port }};
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,38 +1,107 @@
|
|||||||
|
---
|
||||||
|
# Backend Deployment
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "oramap.fullname" . }}
|
name: {{ include "oramap.fullname" . }}-backend
|
||||||
labels:
|
labels:
|
||||||
app: {{ include "oramap.name" . }}
|
app: {{ include "oramap.name" . }}-backend
|
||||||
chart: {{ include "oramap.chart" . }}
|
chart: {{ include "oramap.chart" . }}
|
||||||
release: {{ .Release.Name }}
|
release: {{ .Release.Name }}
|
||||||
heritage: {{ .Release.Service }}
|
heritage: {{ .Release.Service }}
|
||||||
|
component: backend
|
||||||
spec:
|
spec:
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: {{ .Values.backend.replicaCount }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: {{ include "oramap.name" . }}
|
app: {{ include "oramap.name" . }}-backend
|
||||||
release: {{ .Release.Name }}
|
release: {{ .Release.Name }}
|
||||||
|
component: backend
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: {{ include "oramap.name" . }}
|
app: {{ include "oramap.name" . }}-backend
|
||||||
release: {{ .Release.Name }}
|
release: {{ .Release.Name }}
|
||||||
|
component: backend
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: backend
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.containerPort }}
|
- containerPort: {{ .Values.backend.containerPort }}
|
||||||
name: http
|
name: http
|
||||||
|
env:
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: "production"
|
||||||
|
- name: PORT
|
||||||
|
value: "{{ .Values.backend.containerPort }}"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.backend.resources | nindent 12 }}
|
||||||
|
---
|
||||||
|
# Frontend Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "oramap.fullname" . }}-frontend
|
||||||
|
labels:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
chart: {{ include "oramap.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
component: frontend
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.frontend.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
component: frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
component: frontend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.frontend.containerPort }}
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-config
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: http
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: http
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- toYaml .Values.frontend.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: nginx-config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "oramap.fullname" . }}-nginx-config
|
||||||
|
|||||||
@ -21,9 +21,9 @@ spec:
|
|||||||
pathType: {{ .pathType }}
|
pathType: {{ .pathType }}
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ include "oramap.fullname" $ }}
|
name: {{ include "oramap.fullname" $ }}-frontend
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.service.port }}
|
number: {{ $.Values.service.frontend.port }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.ingress.tls }}
|
{{- if .Values.ingress.tls }}
|
||||||
|
|||||||
@ -1,19 +1,46 @@
|
|||||||
|
---
|
||||||
|
# Backend Service
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "oramap.fullname" . }}
|
name: {{ include "oramap.fullname" . }}-backend
|
||||||
labels:
|
labels:
|
||||||
app: {{ include "oramap.name" . }}
|
app: {{ include "oramap.name" . }}-backend
|
||||||
chart: {{ include "oramap.chart" . }}
|
chart: {{ include "oramap.chart" . }}
|
||||||
release: {{ .Release.Name }}
|
release: {{ .Release.Name }}
|
||||||
heritage: {{ .Release.Service }}
|
heritage: {{ .Release.Service }}
|
||||||
|
component: backend
|
||||||
spec:
|
spec:
|
||||||
type: {{ .Values.service.type }}
|
type: {{ .Values.service.type }}
|
||||||
ports:
|
ports:
|
||||||
- port: {{ .Values.service.port }}
|
- port: {{ .Values.service.backend.port }}
|
||||||
targetPort: {{ .Values.containerPort }}
|
targetPort: {{ .Values.service.backend.targetPort }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: http
|
name: http
|
||||||
selector:
|
selector:
|
||||||
app: {{ include "oramap.name" . }}
|
app: {{ include "oramap.name" . }}-backend
|
||||||
release: {{ .Release.Name }}
|
release: {{ .Release.Name }}
|
||||||
|
component: backend
|
||||||
|
---
|
||||||
|
# Frontend Service
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "oramap.fullname" . }}-frontend
|
||||||
|
labels:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
chart: {{ include "oramap.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
component: frontend
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.frontend.port }}
|
||||||
|
targetPort: {{ .Values.service.frontend.targetPort }}
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: {{ include "oramap.name" . }}-frontend
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
component: frontend
|
||||||
|
|||||||
@ -1,15 +1,45 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
# Backend API configuration
|
||||||
repository: harbor.dvirlabs.com/shay/oramap
|
backend:
|
||||||
tag: "1"
|
image:
|
||||||
|
repository: harbor.dvirlabs.com/my-apps/oramap-backend
|
||||||
|
tag: "latest"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
containerPort: 3000
|
||||||
|
replicaCount: 1
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
|
||||||
containerPort: 3000
|
# Frontend Nginx configuration
|
||||||
|
frontend:
|
||||||
|
image:
|
||||||
|
repository: harbor.dvirlabs.com/my-apps/oramap-frontend
|
||||||
|
tag: "latest"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
containerPort: 80
|
||||||
|
replicaCount: 1
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 256Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
backend:
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
frontend:
|
||||||
port: 80
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>🗺️ Ora</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>🗺️ Ora</h1>
|
|
||||||
<div class="search-bar">
|
|
||||||
<input type="text" id="searchInput" placeholder="Enter family name..." />
|
|
||||||
<button onclick="searchFamily()">Search</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<div id="map"></div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.min.js"></script>
|
|
||||||
<script src="script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 975 KiB |
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
124
public/script.js
124
public/script.js
@ -1,124 +0,0 @@
|
|||||||
let map = L.map('map').setView([15.5527, 48.5164], 6);
|
|
||||||
|
|
||||||
// Define the two tile layers
|
|
||||||
const voyager = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
|
|
||||||
attribution: '© OpenStreetMap contributors © CARTO',
|
|
||||||
subdomains: 'abcd',
|
|
||||||
maxZoom: 19
|
|
||||||
});
|
|
||||||
|
|
||||||
const openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
attribution: 'Map data © OpenStreetMap contributors',
|
|
||||||
maxZoom: 19
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the default layer (Voyager)
|
|
||||||
voyager.addTo(map);
|
|
||||||
|
|
||||||
// Group the layers for switching
|
|
||||||
const baseMaps = {
|
|
||||||
"English Map (CartoDB Voyager)": voyager,
|
|
||||||
"Original Map (OpenStreetMap)": openStreetMap
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the layer control to the map
|
|
||||||
L.control.layers(baseMaps).addTo(map);
|
|
||||||
|
|
||||||
async function searchFamily() {
|
|
||||||
const familyName = document.getElementById('searchInput').value.trim();
|
|
||||||
if (!familyName) {
|
|
||||||
alert('Please enter a family name.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/search?family=${encodeURIComponent(familyName)}`);
|
|
||||||
const familyResult = await response.json();
|
|
||||||
|
|
||||||
if (!familyResult.length) {
|
|
||||||
alert('No matching family found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMarkers();
|
|
||||||
|
|
||||||
familyResult.forEach(record => {
|
|
||||||
L.marker([record.lat, record.lng]).addTo(map)
|
|
||||||
.bindPopup(`<strong>${record.family}</strong><br>City: ${record.city}`)
|
|
||||||
.openPopup();
|
|
||||||
});
|
|
||||||
|
|
||||||
const first = familyResult[0];
|
|
||||||
map.setView([first.lat, first.lng], 7);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Search error:', error);
|
|
||||||
alert('Something went wrong while searching. Please try again later.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearMarkers() {
|
|
||||||
map.eachLayer(layer => {
|
|
||||||
if (layer instanceof L.Marker) {
|
|
||||||
map.removeLayer(layer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const familyNames = [
|
|
||||||
"Kafe (קאפח)", "Shiheb (שחב-שבח)", "Eraki (עראקי)", "Salumi (סלומי-שלומי)",
|
|
||||||
"Afgin (עפג'ין)", "Uzeyri (עזירי-עוזרי)"
|
|
||||||
// Add more families here if you like
|
|
||||||
];
|
|
||||||
|
|
||||||
// Initialize Fuse.js
|
|
||||||
const fuse = new Fuse(familyNames, {
|
|
||||||
includeScore: true,
|
|
||||||
threshold: 0.4 // Lower = stricter matching
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchInput = document.getElementById('searchInput');
|
|
||||||
const suggestionsBox = document.createElement('div');
|
|
||||||
suggestionsBox.classList.add('suggestions');
|
|
||||||
searchInput.parentNode.style.position = 'relative'; // Make parent relative
|
|
||||||
searchInput.parentNode.appendChild(suggestionsBox);
|
|
||||||
|
|
||||||
searchInput.addEventListener('input', () => {
|
|
||||||
const value = searchInput.value.trim();
|
|
||||||
suggestionsBox.innerHTML = '';
|
|
||||||
|
|
||||||
if (!value) return;
|
|
||||||
|
|
||||||
const results = fuse.search(value);
|
|
||||||
|
|
||||||
results.forEach(result => {
|
|
||||||
const option = document.createElement('div');
|
|
||||||
option.textContent = result.item;
|
|
||||||
option.onclick = () => {
|
|
||||||
searchInput.value = result.item;
|
|
||||||
suggestionsBox.innerHTML = '';
|
|
||||||
};
|
|
||||||
suggestionsBox.appendChild(option);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
L.control.logo = function (opts) {
|
|
||||||
return new L.Control.Logo(opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
L.Control.Logo = L.Control.extend({
|
|
||||||
onAdd: function () {
|
|
||||||
const div = L.DomUtil.create('div', 'custom-logo');
|
|
||||||
div.innerHTML = `
|
|
||||||
<img src="logo.png" alt="Logo" style="height: 40px; vertical-align: middle;">
|
|
||||||
<span style="margin-left: 8px; font-weight: bold; color: #333;">Shevach</span>
|
|
||||||
`;
|
|
||||||
return div;
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove: function () {
|
|
||||||
// Nothing to clean up
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.control.logo({ position: 'bottomleft' }).addTo(map);
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
/* Reset some basic styles */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: #f4f4f4;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header styles */
|
|
||||||
header {
|
|
||||||
background-color: #2c3e50;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar input {
|
|
||||||
padding: 10px;
|
|
||||||
width: 250px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #3498db;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestions {
|
|
||||||
background: #363251;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
position: absolute;
|
|
||||||
top: 110%; /* Just below the input */
|
|
||||||
left: 48%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
max-width: fit-content;
|
|
||||||
min-width: 200px; /* Optional: minimum size */
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestions div {
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestions div:hover {
|
|
||||||
background-color: #95a1a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-logo {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 6px 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map container */
|
|
||||||
#map {
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 80vh;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main container */
|
|
||||||
main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
npm init -y
|
|
||||||
npm install express
|
|
||||||
node server.js
|
|
||||||
19
server.js
19
server.js
@ -1,19 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const app = express();
|
|
||||||
const families = require('./data/families.json');
|
|
||||||
|
|
||||||
app.use(express.static('public'));
|
|
||||||
|
|
||||||
app.get('/search', (req, res) => {
|
|
||||||
const query = req.query.family?.toLowerCase();
|
|
||||||
if (!query) {
|
|
||||||
return res.json([]);
|
|
||||||
}
|
|
||||||
const matches = families.filter(fam => fam.family.toLowerCase().includes(query));
|
|
||||||
res.json(matches);
|
|
||||||
});
|
|
||||||
|
|
||||||
const port = 3000;
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`Server running at http://localhost:${port}`);
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user