diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d3e45d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package-lock.json +package.json + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c179cde --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM node:24-slim + +WORKDIR /app + +COPY package*.json ./ +RUN npm install + +COPY . . + +CMD ["node", "server.js"] diff --git a/README copy.md b/README copy.md new file mode 100644 index 0000000..5ca3466 --- /dev/null +++ b/README copy.md @@ -0,0 +1,6 @@ +# How to Run +``` +npm init -y +npm install express +node server.js +``` diff --git a/data/families.json b/data/families.json new file mode 100644 index 0000000..f677668 --- /dev/null +++ b/data/families.json @@ -0,0 +1,10 @@ +[ + { "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 } + ] \ No newline at end of file diff --git a/oramap/.helmignore b/oramap/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/oramap/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/oramap/Chart.yaml b/oramap/Chart.yaml new file mode 100644 index 0000000..c7a98b5 --- /dev/null +++ b/oramap/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: oramap +description: A Helm chart for Kubernetes + +# 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 + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# 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 +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/oramap/templates/_helpers.tpl b/oramap/templates/_helpers.tpl new file mode 100644 index 0000000..1767bb5 --- /dev/null +++ b/oramap/templates/_helpers.tpl @@ -0,0 +1,11 @@ +{{- define "oramap.name" -}} +{{ .Chart.Name }} +{{- end }} + +{{- define "oramap.fullname" -}} +{{ include "oramap.name" . }}-{{ .Release.Name }} +{{- end }} + +{{- define "oramap.chart" -}} +{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} +{{- end }} \ No newline at end of file diff --git a/oramap/templates/deployment.yaml b/oramap/templates/deployment.yaml new file mode 100644 index 0000000..e6eff7f --- /dev/null +++ b/oramap/templates/deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oramap.fullname" . }} + labels: + app: {{ include "oramap.name" . }} + chart: {{ include "oramap.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ include "oramap.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ include "oramap.name" . }} + release: {{ .Release.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.containerPort }} + name: http + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} diff --git a/oramap/templates/ingress.yaml b/oramap/templates/ingress.yaml new file mode 100644 index 0000000..04dabe8 --- /dev/null +++ b/oramap/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "oramap.fullname" . }} + annotations: + {{- if .Values.ingress.className }} + kubernetes.io/ingress.class: {{ .Values.ingress.className }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "oramap.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} +{{- end }} diff --git a/oramap/templates/service.yaml b/oramap/templates/service.yaml new file mode 100644 index 0000000..da296c8 --- /dev/null +++ b/oramap/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "oramap.fullname" . }} + labels: + app: {{ include "oramap.name" . }} + chart: {{ include "oramap.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.containerPort }} + protocol: TCP + name: http + selector: + app: {{ include "oramap.name" . }} + release: {{ .Release.Name }} diff --git a/oramap/values.yaml b/oramap/values.yaml new file mode 100644 index 0000000..a110b9d --- /dev/null +++ b/oramap/values.yaml @@ -0,0 +1,22 @@ +replicaCount: 1 + +image: + repository: harbor.dvirlabs.com/shay/oramap + tag: "1" + pullPolicy: IfNotPresent + +containerPort: 3000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: true + className: "traefik" + hosts: + - host: oramap.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: [] diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..86d16e9 --- /dev/null +++ b/public/index.html @@ -0,0 +1,26 @@ + + + + + 🗺️ Ora + + + + +
+

🗺️ Ora

+ +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/public/logo-shay.png b/public/logo-shay.png new file mode 100644 index 0000000..75c5947 Binary files /dev/null and b/public/logo-shay.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..b2425c2 Binary files /dev/null and b/public/logo.png differ diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..f1057e7 --- /dev/null +++ b/public/script.js @@ -0,0 +1,124 @@ +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(`/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(`${record.family}
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 = ` + Logo + Shevach + `; + return div; + }, + + onRemove: function () { + // Nothing to clean up + } +}); + +L.control.logo({ position: 'bottomleft' }).addTo(map); \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..12ddc35 --- /dev/null +++ b/public/style.css @@ -0,0 +1,98 @@ +/* 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; + } \ No newline at end of file diff --git a/run_ora_map.sh b/run_ora_map.sh new file mode 100644 index 0000000..428c8a6 --- /dev/null +++ b/run_ora_map.sh @@ -0,0 +1,3 @@ +npm init -y +npm install express +node server.js diff --git a/server.js b/server.js new file mode 100644 index 0000000..2e8b56a --- /dev/null +++ b/server.js @@ -0,0 +1,19 @@ +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}`); +}); \ No newline at end of file