Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
872ebc1971
|
||
|
|
3934cbf3ae
|
||
|
|
36f7ce8e5d
|
@@ -0,0 +1,19 @@
|
||||
FROM node:20-slim AS frontendbuilder
|
||||
|
||||
WORKDIR /tmp/frontendbuilder
|
||||
COPY public/ .
|
||||
RUN npm ci && npm run build
|
||||
|
||||
|
||||
FROM python:3.11.4-slim-buster
|
||||
|
||||
WORKDIR /var/www/html/
|
||||
COPY . .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY --from=frontendbuilder /tmp/frontendbuilder/dist/ /var/www/html/public
|
||||
|
||||
VOLUME /var/www/html/public
|
||||
|
||||
CMD uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: .build/prod/app.Dockerfile
|
||||
hostname: mapmaker
|
||||
image: git.shihaam.dev/sarlink/mapmaker/api
|
||||
nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./nginx.Dockerfile
|
||||
hostname: mapmaker-nginx
|
||||
image: git.shihaam.dev/sarlink/mapmaker/nginx
|
||||
+10
-13
@@ -6,7 +6,7 @@ server {
|
||||
error_log /dev/stdout info;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8000/api/;
|
||||
proxy_pass http://mapmaker:8000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -15,7 +15,7 @@ server {
|
||||
}
|
||||
|
||||
location /ws/ {
|
||||
proxy_pass http://api:8000/ws/;
|
||||
proxy_pass http://mapmaker:8000/ws/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
@@ -23,22 +23,19 @@ server {
|
||||
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;
|
||||
|
||||
# WebSocket timeout settings
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_connect_timeout 60s;
|
||||
}
|
||||
|
||||
location / {
|
||||
# In development, proxy to Vite dev server
|
||||
proxy_pass http://node:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
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;
|
||||
root /var/www/html/public;
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM node:24-bookworm-slim
|
||||
|
||||
WORKDIR /var/www/html/public
|
||||
|
||||
CMD npm run dev -- --host 0.0.0.0
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM python:3.11.4-slim-buster
|
||||
|
||||
WORKDIR /var/www/html/
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Build and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: Build and Push Docker Images
|
||||
runs-on: builder
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to Docker Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ vars.DOCKER_REGISTRY_URL }}
|
||||
username: ${{ vars.DOCKER_REGISTRY_USER }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Build and push docker images
|
||||
working-directory: .build/prod
|
||||
run: docker compose --progress plain build --push
|
||||
|
||||
|
||||
- name: Deploy production
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
ssh root@10.0.1.36 -t "cd /mnt && \
|
||||
docker compose --progress plain pull && \
|
||||
docker compose --progress plain down && \
|
||||
docker compose --progress plain up -d"
|
||||
|
||||
- name: Clean up dangling images
|
||||
if: github.event_name != 'pull_request'
|
||||
run: ssh root@10.0.1.36 -t "docker image prune -f"
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../stores/authStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../stores/authStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../stores/authStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMapEvents, Polyline, Marker } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import { useDrawingStore } from '../../stores/drawingStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { mapItemService } from '../../services/mapItemService';
|
||||
@@ -12,7 +11,7 @@ interface DrawingHandlerProps {
|
||||
}
|
||||
|
||||
export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
||||
const { activeTool, isDrawing, drawingPoints, setIsDrawing, addDrawingPoint, resetDrawing, setActiveTool } =
|
||||
const { activeTool, isDrawing, drawingPoints, setIsDrawing, addDrawingPoint, resetDrawing } =
|
||||
useDrawingStore();
|
||||
const { showToast } = useUIStore();
|
||||
const [cursorPosition, setCursorPosition] = useState<[number, number] | null>(null);
|
||||
@@ -307,11 +306,6 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
||||
|
||||
const dashArray = isWirelessTool ? '10, 10' : undefined;
|
||||
|
||||
// Create preview line from last point to cursor
|
||||
const previewPositions = cursorPosition
|
||||
? [...drawingPoints, cursorPosition]
|
||||
: drawingPoints;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Main line connecting all points */}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
interface LayerInfo {
|
||||
name: string;
|
||||
url: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Polyline, Marker, Popup, Circle, Tooltip, useMapEvents } from 'react-leaflet';
|
||||
import { Polyline, Marker, Popup, Circle, useMapEvents } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import { mapItemService } from '../../services/mapItemService';
|
||||
import { uploadService } from '../../services/uploadService';
|
||||
|
||||
@@ -37,7 +37,7 @@ export function MapView({ mapId, activeLayer, mapLayers, showShareDialog = false
|
||||
};
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
const { isConnected, permission } = useMapWebSocket({
|
||||
const { permission } = useMapWebSocket({
|
||||
mapId: mapId || '',
|
||||
onItemCreated: (item) => {
|
||||
console.log('Real-time item created:', item);
|
||||
|
||||
@@ -51,7 +51,7 @@ export function ShareDialog({ mapId, onClose }: ShareDialogProps) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await mapShareService.shareWithUser(mapId, {
|
||||
user_identifier: newUserId.trim(),
|
||||
user_id: newUserId.trim(),
|
||||
permission: newUserPermission,
|
||||
});
|
||||
setNewUserId('');
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { DrawingTool } from '../../types/mapItem';
|
||||
import { CABLE_COLORS, CABLE_LABELS } from '../../types/mapItem';
|
||||
|
||||
interface ToolbarProps {
|
||||
mapId: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
@@ -81,7 +80,7 @@ const INFO_TOOL: ToolButton = {
|
||||
description: 'Information Marker',
|
||||
};
|
||||
|
||||
export function Toolbar({ mapId, readOnly = false }: ToolbarProps) {
|
||||
export function Toolbar({ readOnly = false }: ToolbarProps) {
|
||||
const { activeTool, setActiveTool } = useDrawingStore();
|
||||
|
||||
const renderIcon = (tool: ToolButton, isDisabled: boolean) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ export function useMapWebSocket({
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [permission, setPermission] = useState<'read' | 'edit' | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const reconnectTimeoutRef = useRef<number | null>(null);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
|
||||
// Use refs for callbacks to avoid reconnecting when they change
|
||||
|
||||
@@ -76,7 +76,7 @@ export function Dashboard() {
|
||||
{selectedMapId && (
|
||||
<>
|
||||
<div className="border-t border-gray-200 dark:border-gray-700">
|
||||
<Toolbar mapId={selectedMapId} />
|
||||
<Toolbar />
|
||||
</div>
|
||||
|
||||
{/* Map Style section */}
|
||||
@@ -84,7 +84,7 @@ export function Dashboard() {
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 px-1 mb-2">Map Style</h3>
|
||||
<LayerSwitcher
|
||||
activeLayer={activeLayer}
|
||||
onLayerChange={setActiveLayer}
|
||||
onLayerChange={(layer) => setActiveLayer(layer as MapLayer)}
|
||||
layers={MAP_LAYERS}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MapContainer, TileLayer, useMap } from 'react-leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { LayerSwitcher } from '../components/map/LayerSwitcher';
|
||||
import { DrawingHandler } from '../components/map/DrawingHandler';
|
||||
import { MapItemsLayer } from '../components/map/MapItemsLayer';
|
||||
import { Toolbar } from '../components/map/Toolbar';
|
||||
import { MapView } from '../components/map/MapView';
|
||||
import { useMapWebSocket } from '../hooks/useMapWebSocket';
|
||||
import { apiClient } from '../services/api';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
@@ -49,7 +48,6 @@ function MapController() {
|
||||
|
||||
export function SharedMap() {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { darkMode, toggleDarkMode } = useUIStore();
|
||||
const [activeLayer, setActiveLayer] = useState<MapLayer>('osm');
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||
@@ -201,7 +199,7 @@ export function SharedMap() {
|
||||
style={{ zIndex: 9999 }}
|
||||
>
|
||||
<div className="border-t border-gray-200 dark:border-gray-700">
|
||||
<Toolbar mapId={mapData.id} readOnly={isReadOnly} />
|
||||
<Toolbar readOnly={isReadOnly} />
|
||||
</div>
|
||||
|
||||
{/* Map Style section */}
|
||||
@@ -209,7 +207,7 @@ export function SharedMap() {
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 px-1 mb-2">Map Style</h3>
|
||||
<LayerSwitcher
|
||||
activeLayer={activeLayer}
|
||||
onLayerChange={setActiveLayer}
|
||||
onLayerChange={(layer) => setActiveLayer(layer as MapLayer)}
|
||||
layers={MAP_LAYERS}
|
||||
/>
|
||||
</div>
|
||||
@@ -229,7 +227,7 @@ export function SharedMap() {
|
||||
url={MAP_LAYERS[activeLayer].url}
|
||||
attribution={MAP_LAYERS[activeLayer].attribution}
|
||||
maxZoom={MAP_LAYERS[activeLayer].maxZoom}
|
||||
maxNativeZoom={MAP_LAYERS[activeLayer].maxNativeZoom}
|
||||
maxNativeZoom={(MAP_LAYERS[activeLayer] as any).maxNativeZoom}
|
||||
/>
|
||||
|
||||
{/* Drawing handler for edit access */}
|
||||
|
||||
Reference in New Issue
Block a user