diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aab7b37..ecfa1c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +# 0.24.2 - 2025-02-15 + +## Fixed + +- Fixed a bug where background jobs to import Immich and Photoprism geolocation data data could not be created by non-admin users. +- Fixed a bug where upon point deletion there was an error it was not being removed from the map, while it was actually deleted from the database. #883 + +### Changed + +- Restrict access to Sidekiq in non self-hosted mode. +- Restrict access to background jobs in non self-hosted mode. +- Restrict access to users management in non self-hosted mode. + # 0.24.1 - 2025-02-13 ## Custom map tiles diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6d104eab..7b7c27d0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base include Pundit::Authorization - before_action :unread_notifications + before_action :unread_notifications, :set_self_hosted_status protected @@ -18,4 +18,16 @@ class ApplicationController < ActionController::Base redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other end + + def authenticate_self_hosted! + return if DawarichSettings.self_hosted? + + redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other + end + + private + + def set_self_hosted_status + @self_hosted = DawarichSettings.self_hosted? + end end diff --git a/app/controllers/settings/background_jobs_controller.rb b/app/controllers/settings/background_jobs_controller.rb index 8079b7e5..6eafb4c7 100644 --- a/app/controllers/settings/background_jobs_controller.rb +++ b/app/controllers/settings/background_jobs_controller.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class Settings::BackgroundJobsController < ApplicationController - before_action :authenticate_user! - before_action :authenticate_admin! + before_action :authenticate_self_hosted! + before_action :authenticate_admin!, unless: lambda { + %w[start_immich_import start_photoprism_import].include?(params[:job_name]) + } def index @queues = Sidekiq::Queue.all @@ -13,7 +15,15 @@ class Settings::BackgroundJobsController < ApplicationController flash.now[:notice] = 'Job was successfully created.' - redirect_to settings_background_jobs_path, notice: 'Job was successfully created.' + redirect_path = + case params[:job_name] + when 'start_immich_import', 'start_photoprism_import' + imports_path + else + settings_background_jobs_path + end + + redirect_to redirect_path, notice: 'Job was successfully created.' end def destroy diff --git a/app/controllers/settings/users_controller.rb b/app/controllers/settings/users_controller.rb index 529785db..046d34ac 100644 --- a/app/controllers/settings/users_controller.rb +++ b/app/controllers/settings/users_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Settings::UsersController < ApplicationController - before_action :authenticate_user! before_action :authenticate_admin! + before_action :authenticate_self_hosted! def index @users = User.order(created_at: :desc) diff --git a/app/javascript/controllers/base_controller.js b/app/javascript/controllers/base_controller.js new file mode 100644 index 00000000..ab6f12f7 --- /dev/null +++ b/app/javascript/controllers/base_controller.js @@ -0,0 +1,23 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + selfHosted: Boolean + } + + // Every controller that extends BaseController and uses initialize() + // should call super.initialize() + // Example: + // export default class extends BaseController { + // initialize() { + // super.initialize() + // } + // } + initialize() { + // Get the self-hosted value from the HTML root element + if (!this.hasSelfHostedValue) { + const selfHosted = document.documentElement.dataset.selfHosted === 'true' + this.selfHostedValue = selfHosted + } + } +} diff --git a/app/javascript/controllers/checkbox_select_all_controller.js b/app/javascript/controllers/checkbox_select_all_controller.js index 5e77773f..1b542f84 100644 --- a/app/javascript/controllers/checkbox_select_all_controller.js +++ b/app/javascript/controllers/checkbox_select_all_controller.js @@ -1,7 +1,7 @@ -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" // Connects to data-controller="checkbox-select-all" -export default class extends Controller { +export default class extends BaseController { static targets = ["parent", "child"] connect() { diff --git a/app/javascript/controllers/datetime_controller.js b/app/javascript/controllers/datetime_controller.js index b56f07e3..b03df4ca 100644 --- a/app/javascript/controllers/datetime_controller.js +++ b/app/javascript/controllers/datetime_controller.js @@ -2,9 +2,9 @@ // - trips/new // - trips/edit -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" -export default class extends Controller { +export default class extends BaseController { static targets = ["startedAt", "endedAt", "apiKey"] static values = { tripsId: String } diff --git a/app/javascript/controllers/imports_controller.js b/app/javascript/controllers/imports_controller.js index fd00d5c9..d39455a0 100644 --- a/app/javascript/controllers/imports_controller.js +++ b/app/javascript/controllers/imports_controller.js @@ -1,7 +1,7 @@ -import { Controller } from "@hotwired/stimulus"; +import BaseController from "./base_controller"; import consumer from "../channels/consumer"; -export default class extends Controller { +export default class extends BaseController { static targets = ["index"]; connect() { diff --git a/app/javascript/controllers/map_preview_controller.js b/app/javascript/controllers/map_preview_controller.js index 3b610a33..e55f2b83 100644 --- a/app/javascript/controllers/map_preview_controller.js +++ b/app/javascript/controllers/map_preview_controller.js @@ -1,8 +1,8 @@ -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" import L from "leaflet" import { showFlashMessage } from "../maps/helpers" -export default class extends Controller { +export default class extends BaseController { static targets = ["urlInput", "mapContainer", "saveButton"] DEFAULT_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index d2f59dbb..9868368a 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -13,26 +13,16 @@ import { import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas"; -import { showFlashMessage, fetchAndDisplayPhotos, debounce } from "../maps/helpers"; - -import { - osmMapLayer, - osmHotMapLayer, - OPNVMapLayer, - openTopoMapLayer, - cyclOsmMapLayer, - esriWorldStreetMapLayer, - esriWorldTopoMapLayer, - esriWorldImageryMapLayer, - esriWorldGrayCanvasMapLayer -} from "../maps/layers"; +import { showFlashMessage, fetchAndDisplayPhotos } from "../maps/helpers"; import { countryCodesMap } from "../maps/country_codes"; import "leaflet-draw"; import { initializeFogCanvas, drawFogCanvas, createFogOverlay } from "../maps/fog_of_war"; import { TileMonitor } from "../maps/tile_monitor"; +import BaseController from "./base_controller"; +import { createAllMapLayers } from "../maps/layers"; -export default class extends Controller { +export default class extends BaseController { static targets = ["container"]; settingsButtonAdded = false; @@ -41,6 +31,7 @@ export default class extends Controller { trackedMonthsCache = null; connect() { + super.connect(); console.log("Map controller connected"); this.apiKey = this.element.dataset.api_key; @@ -402,17 +393,7 @@ export default class extends Controller { baseMaps() { let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap"; - let maps = { - OpenStreetMap: osmMapLayer(this.map, selectedLayerName), - "OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName), - OPNV: OPNVMapLayer(this.map, selectedLayerName), - openTopo: openTopoMapLayer(this.map, selectedLayerName), - cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName), - esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName), - esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName), - esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName), - esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName) - }; + let maps = createAllMapLayers(this.map, selectedLayerName); // Add custom map if it exists in settings if (this.userSettings.maps && this.userSettings.maps.url) { @@ -536,13 +517,13 @@ export default class extends Controller { if (this.layerControl) { this.map.removeControl(this.layerControl); const controlsLayer = { - Points: this.markersLayer, - Routes: this.polylinesLayer, - Heatmap: this.heatmapLayer, - "Fog of War": this.fogOverlay, - "Scratch map": this.scratchLayer, - Areas: this.areasLayer, - Photos: this.photoMarkers + Points: this.markersLayer || L.layerGroup(), + Routes: this.polylinesLayer || L.layerGroup(), + Heatmap: this.heatmapLayer || L.layerGroup(), + "Fog of War": new this.fogOverlay(), + "Scratch map": this.scratchLayer || L.layerGroup(), + Areas: this.areasLayer || L.layerGroup(), + Photos: this.photoMarkers || L.layerGroup() }; this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map); } diff --git a/app/javascript/controllers/notifications_controller.js b/app/javascript/controllers/notifications_controller.js index 6ba44514..c40a4db5 100644 --- a/app/javascript/controllers/notifications_controller.js +++ b/app/javascript/controllers/notifications_controller.js @@ -1,11 +1,12 @@ -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" import consumer from "../channels/consumer" -export default class extends Controller { +export default class extends BaseController { static targets = ["badge", "list"] static values = { userId: Number } initialize() { + super.initialize() this.subscription = null } diff --git a/app/javascript/controllers/removals_controller.js b/app/javascript/controllers/removals_controller.js index cf487d07..c5f30b32 100644 --- a/app/javascript/controllers/removals_controller.js +++ b/app/javascript/controllers/removals_controller.js @@ -1,6 +1,6 @@ -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" -export default class extends Controller { +export default class extends BaseController { static values = { timeout: Number } diff --git a/app/javascript/controllers/trip_map_controller.js b/app/javascript/controllers/trip_map_controller.js index 1bbdc207..01b4a9e5 100644 --- a/app/javascript/controllers/trip_map_controller.js +++ b/app/javascript/controllers/trip_map_controller.js @@ -1,10 +1,10 @@ // This controller is being used on: // - trips/index -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" import L from "leaflet" -export default class extends Controller { +export default class extends BaseController { static values = { tripId: Number, path: String, diff --git a/app/javascript/controllers/trips_controller.js b/app/javascript/controllers/trips_controller.js index 974feb30..6dc0c544 100644 --- a/app/javascript/controllers/trips_controller.js +++ b/app/javascript/controllers/trips_controller.js @@ -3,7 +3,7 @@ // - trips/edit // - trips/new -import { Controller } from "@hotwired/stimulus" +import BaseController from "./base_controller" import L from "leaflet" import { osmMapLayer, @@ -22,7 +22,7 @@ import { showFlashMessage } from '../maps/helpers'; -export default class extends Controller { +export default class extends BaseController { static targets = ["container", "startedAt", "endedAt"] static values = { } diff --git a/app/javascript/controllers/visit_modal_map_controller.js b/app/javascript/controllers/visit_modal_map_controller.js index 5fcb0547..f9b164f6 100644 --- a/app/javascript/controllers/visit_modal_map_controller.js +++ b/app/javascript/controllers/visit_modal_map_controller.js @@ -1,12 +1,12 @@ -import { Controller } from "@hotwired/stimulus" -import L, { latLng } from "leaflet"; -import { osmMapLayer } from "../maps/layers"; +import BaseController from "./base_controller" +import L from "leaflet" +import { osmMapLayer } from "../maps/layers" // This controller is used to display a map of all coordinates for a visit // on the "Map" modal of a visit on the Visits page -export default class extends Controller { - static targets = ["container"]; +export default class extends BaseController { + static targets = ["container"] connect() { this.coordinates = JSON.parse(this.element.dataset.coordinates); diff --git a/app/javascript/controllers/visit_modal_places_controller.js b/app/javascript/controllers/visit_modal_places_controller.js index ad6259f2..b697622e 100644 --- a/app/javascript/controllers/visit_modal_places_controller.js +++ b/app/javascript/controllers/visit_modal_places_controller.js @@ -1,10 +1,13 @@ -import { Controller } from "@hotwired/stimulus"; +import BaseController from "./base_controller" -export default class extends Controller { +export default class extends BaseController { + static targets = ["name", "input"] connect() { - this.visitId = this.element.dataset.id; this.apiKey = this.element.dataset.api_key; + this.visitId = this.element.dataset.id; + + this.element.addEventListener("visit-name:updated", this.updateAll.bind(this)); } // Action to handle selection change diff --git a/app/javascript/controllers/visit_name_controller.js b/app/javascript/controllers/visit_name_controller.js index 70af33b2..24b33273 100644 --- a/app/javascript/controllers/visit_name_controller.js +++ b/app/javascript/controllers/visit_name_controller.js @@ -1,7 +1,7 @@ -import { Controller } from "@hotwired/stimulus"; +import BaseController from "./base_controller" // This controller is used to handle the updating of visit names on the Visits page -export default class extends Controller { +export default class extends BaseController { static targets = ["name", "input"]; connect() { diff --git a/app/javascript/maps/helpers.js b/app/javascript/maps/helpers.js index 7c850f03..59234a98 100644 --- a/app/javascript/maps/helpers.js +++ b/app/javascript/maps/helpers.js @@ -87,10 +87,19 @@ export function haversineDistance(lat1, lon1, lat2, lon2, unit = 'km') { } export function showFlashMessage(type, message) { - // Create the outer flash container div + // Get or create the flash container + let flashContainer = document.getElementById('flash-messages'); + if (!flashContainer) { + flashContainer = document.createElement('div'); + flashContainer.id = 'flash-messages'; + flashContainer.className = 'fixed top-5 right-5 flex flex-col-reverse gap-2 z-40'; + document.body.appendChild(flashContainer); + } + + // Create the flash message div const flashDiv = document.createElement('div'); flashDiv.setAttribute('data-controller', 'removals'); - flashDiv.className = `flex items-center fixed top-5 right-5 ${classesForFlash(type)} py-3 px-5 rounded-lg`; + flashDiv.className = `flex items-center justify-between ${classesForFlash(type)} py-3 px-5 rounded-lg z-40`; // Create the message div const messageDiv = document.createElement('div'); @@ -101,6 +110,7 @@ export function showFlashMessage(type, message) { const closeButton = document.createElement('button'); closeButton.setAttribute('type', 'button'); closeButton.setAttribute('data-action', 'click->removals#remove'); + closeButton.className = 'ml-auto'; // Ensures button stays on the right // Create the SVG icon for the close button const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); @@ -116,21 +126,22 @@ export function showFlashMessage(type, message) { closeIconPath.setAttribute('stroke-width', '2'); closeIconPath.setAttribute('d', 'M6 18L18 6M6 6l12 12'); - // Append the path to the SVG + // Append all elements closeIcon.appendChild(closeIconPath); - // Append the SVG to the close button closeButton.appendChild(closeIcon); - - // Append the message and close button to the flash div flashDiv.appendChild(messageDiv); flashDiv.appendChild(closeButton); + flashContainer.appendChild(flashDiv); - // Append the flash message to the body or a specific flash container - document.body.appendChild(flashDiv); - - // Optional: Automatically remove the flash message after 5 seconds + // Automatically remove after 5 seconds setTimeout(() => { - flashDiv.remove(); + if (flashDiv && flashDiv.parentNode) { + flashDiv.remove(); + // Remove container if empty + if (flashContainer && !flashContainer.hasChildNodes()) { + flashContainer.remove(); + } + } }, 5000); } diff --git a/app/javascript/maps/layers.js b/app/javascript/maps/layers.js index c32200cc..6acf3d77 100644 --- a/app/javascript/maps/layers.js +++ b/app/javascript/maps/layers.js @@ -1,4 +1,38 @@ -// Yeah I know it should be DRY but this is me doing a KISS at 21:00 on a Sunday night +// Import the maps configuration +// In non-self-hosted mode, we need to mount external maps_config.js to the container +import { mapsConfig } from './maps_config'; + +export function createMapLayer(map, selectedLayerName, layerKey) { + const config = mapsConfig[layerKey]; + + if (!config) { + console.warn(`No configuration found for layer: ${layerKey}`); + return null; + } + + let layer = L.tileLayer(config.url, { + maxZoom: config.maxZoom, + attribution: config.attribution, + // Add any other config properties that might be needed + }); + + if (selectedLayerName === layerKey) { + return layer.addTo(map); + } else { + return layer; + } +} + +// Helper function to create all map layers +export function createAllMapLayers(map, selectedLayerName) { + const layers = {}; + + Object.keys(mapsConfig).forEach(layerKey => { + layers[layerKey] = createMapLayer(map, selectedLayerName, layerKey); + }); + + return layers; +} export function osmMapLayer(map, selectedLayerName) { let layerName = 'OpenStreetMap'; @@ -57,166 +91,6 @@ export function openTopoMapLayer(map, selectedLayerName) { } } -// export function stadiaAlidadeSmoothMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaAlidadeSmooth'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaAlidadeSmoothDarkMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaAlidadeSmoothDark'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaAlidadeSatelliteMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaAlidadeSatellite'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_satellite/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© CNES, Distribution Airbus DS, © Airbus DS, © PlanetObserver (Contains Copernicus Data) | © Stadia Maps © OpenMapTiles © OpenStreetMap contributors', -// ext: 'jpg' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaOsmBrightMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaOsmBright'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaOutdoorMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaOutdoor'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaStamenTonerMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaStamenToner'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaStamenTonerBackgroundMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaStamenTonerBackground'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner_background/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaStamenTonerLiteMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaStamenTonerLite'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner_lite/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 20, -// attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaStamenWatercolorMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaStamenWatercolor'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.{ext}', { -// minZoom: 1, -// maxZoom: 16, -// attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', -// ext: 'jpg' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - -// export function stadiaStamenTerrainMapLayer(map, selectedLayerName) { -// let layerName = 'stadiaStamenTerrain'; -// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}{r}.{ext}', { -// minZoom: 0, -// maxZoom: 18, -// attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', -// ext: 'png' -// }); - -// if (selectedLayerName === layerName) { -// return layer.addTo(map); -// } else { -// return layer; -// } -// } - export function cyclOsmMapLayer(map, selectedLayerName) { let layerName = 'cyclOsm'; let layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', { diff --git a/app/javascript/maps/maps_config.js b/app/javascript/maps/maps_config.js new file mode 100644 index 00000000..c0017df6 --- /dev/null +++ b/app/javascript/maps/maps_config.js @@ -0,0 +1,44 @@ +export const mapsConfig = { + "OpenStreetMap": { + url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + maxZoom: 19, + attribution: "© OpenStreetMap" + }, + "OpenStreetMap.HOT": { + url: "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", + maxZoom: 19, + attribution: "© OpenStreetMap contributors, Tiles style by Humanitarian OpenStreetMap Team hosted by OpenStreetMap France" + }, + "OPNV": { + url: "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png", + maxZoom: 18, + attribution: "Map memomaps.de CC-BY-SA, map data © OpenStreetMap contributors" + }, + "openTopo": { + url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", + maxZoom: 17, + attribution: "Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap (CC-BY-SA)" + }, + "cyclOsm": { + url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png", + maxZoom: 20, + attribution: "CyclOSM | Map data: © OpenStreetMap contributors" + }, + "esriWorldStreet": { + url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}", + attribution: "Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012" + }, + "esriWorldTopo": { + url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", + attribution: "Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community" + }, + "esriWorldImagery": { + url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", + attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" + }, + "esriWorldGrayCanvas": { + url: "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}", + attribution: "Tiles © Esri — Esri, DeLorme, NAVTEQ", + maxZoom: 16 + } +}; diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4063fad1..f41baeda 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,5 +1,5 @@ - + <%= full_title(yield(:title)) %> diff --git a/app/views/settings/_navigation.html.erb b/app/views/settings/_navigation.html.erb index 3f5aa627..8b5e51e0 100644 --- a/app/views/settings/_navigation.html.erb +++ b/app/views/settings/_navigation.html.erb @@ -1,6 +1,6 @@
<%= link_to 'Integrations', settings_path, role: 'tab', class: "tab #{active_tab?(settings_path)}" %> - <% if current_user.admin? %> + <% if DawarichSettings.self_hosted? && current_user.admin? %> <%= link_to 'Users', settings_users_path, role: 'tab', class: "tab #{active_tab?(settings_users_path)}" %> <%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %> <% end %> diff --git a/app/views/settings/maps/index.html.erb b/app/views/settings/maps/index.html.erb index e80d875a..0910f9b9 100644 --- a/app/views/settings/maps/index.html.erb +++ b/app/views/settings/maps/index.html.erb @@ -1,4 +1,4 @@ -<% content_for :title, "Background jobs" %> +<% content_for :title, "Map settings" %>
<%= render 'settings/navigation' %> diff --git a/app/views/visits/index.html.erb b/app/views/visits/index.html.erb index 3fd03b64..e27dbac2 100644 --- a/app/views/visits/index.html.erb +++ b/app/views/visits/index.html.erb @@ -27,7 +27,7 @@
-