Skip to content

Commit

Permalink
Merge pull request #261 from TorMap/feature/nicknames
Browse files Browse the repository at this point in the history
Feature/nicknames
  • Loading branch information
JuliusHenke authored May 5, 2024
2 parents 60da145 + e69e644 commit dbb84f6
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ class RelayDetailsController(
@PostMapping("family/identifiers")
fun getFamilyIdentifiers(@RequestBody familyIds: List<Long>) =
relayDetailsRepositoryImpl.findFamilyIdentifiers(familyIds)

@Operation(summary = "Return the nicknames of the relays that are associated with a list of relay details IDs.")
@PostMapping("relay/nicknames")
fun getRelayNicknames(@RequestBody ids: List<Long>) = relayDetailsRepositoryImpl.findAllByIdIn(ids).map { it.nickname }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface RelayDetailsRepository : JpaRepository<RelayDetails, Long> {
fun findAllByMonthEqualsAndFamilyEntriesNotNull(month: String): List<RelayDetails>
fun findAllByMonthEqualsAndAutonomousSystemNumberNull(month: String): List<RelayDetails>
fun findAllByFamilyId(familyId: Long): List<RelayDetails>
fun findAllByIdIn(ids: List<Long>): List<RelayDetails>
}
4 changes: 3 additions & 1 deletion frontend/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# The URL pointing to the backend instance (e.g. http://localhost:8080/)
VITE_BACKEND_API_URL=https://api.tormap.org
VITE_BACKEND_API_URL=https://api.tormap.org
VITE_NOWPAYMENTS_API_KEY=BZ1BNEC-VDS47WP-PQB7G6F-9WTFHP8
VITE_DONATION_BITCOIN_ADDRESS=bc1qkpwf376p75uyhqna87p22lt2t9zm99qflz39y3
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "tormap",
"version": "2.2.0",
"private": true,
"type": "module",
"scripts": {
"start": "vite --port 3000",
"build": "tsc && vite build",
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/components/card/BitcoinCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import LightningIcon from '@mui/icons-material/FlashOn';
import WalletIcon from '@mui/icons-material/Wallet';
import {Stack} from '@mui/material';

export default function BitcoinCard() {
const bitcoinAddress = import.meta.env.VITE_DONATION_BITCOIN_ADDRESS

const handleCopyAddress = () => {
navigator.clipboard.writeText(bitcoinAddress);
};

const openInWallet = () => {
window.open(`bitcoin:${bitcoinAddress}`, '_blank');
};

return (
<Card sx={{margin: 'auto', mt: 5, height: 128}}>
<CardContent sx={{paddingBottom: '0'}}>
<Typography gutterBottom variant="subtitle1" component="div">
Bitcoin
</Typography>
<Stack direction="row">
<Typography variant="body2" color="text.secondary">
{bitcoinAddress}
</Typography>
<IconButton size="small" aria-label="copy address" onClick={handleCopyAddress}>
<ContentCopyIcon fontSize="small"/>
</IconButton>
</Stack>
</CardContent>
<CardActions>
<Button size="small" startIcon={<WalletIcon/>} onClick={openInWallet}>
Open Wallet
</Button>
<a href="https://tippin.me/@TorMapOrg" target="_blank" rel="noreferrer noopener">
<Button size="small" startIcon={<LightningIcon/>}>
Lightning
</Button>
</a>
</CardActions>
</Card>
);
}
40 changes: 33 additions & 7 deletions frontend/src/components/dialogs/AboutInformation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ import {
import React, {useState} from "react";

import {ContributorCard} from "../card/ContributorCard";
import {BitcoinIcon} from "../icons/BitcoinIcon";
import {ExternalLink} from "../link/ExternalLink";
import BitcoinCard from "../card/BitcoinCard";
import CardContent from "@mui/material/CardContent";
import LaunchIcon from "@mui/icons-material/Launch";
import Card from "@mui/material/Card";

/**
* A component for displaying information about TorMap
Expand All @@ -35,6 +38,8 @@ export const AboutInformation: React.FunctionComponent = () => {
// App context
const theme = useTheme()
const isLargeScreen = useMediaQuery(theme.breakpoints.up("lg"))
const nowpaymentsApiKey = import.meta.env.VITE_NOWPAYMENTS_API_KEY
const nowpaymentsDonationUrl = `https://nowpayments.io/donation?api_key=${nowpaymentsApiKey}&source=lk_donation&medium=referral`

return (
<>
Expand Down Expand Up @@ -70,10 +75,6 @@ export const AboutInformation: React.FunctionComponent = () => {
<Grid item>
<ExternalLink sx={{color: "white"}} href={"mailto:[email protected]"} label={<EmailIcon/>}/>
</Grid>
<Grid item>
<ExternalLink sx={{color: "white"}} href={"https://tippin.me/@TorMapOrg"}
label={<BitcoinIcon/>}/>
</Grid>
</Grid>
<IconButton aria-label="close" sx={{
position: "absolute",
Expand All @@ -91,9 +92,9 @@ export const AboutInformation: React.FunctionComponent = () => {
TorMap is a world map displaying approximate locations where Tor relays are being hosted.
You can group, filter and analyze thousands of Tor relays, which route anonymous internet
traffic daily. The historic state of the network can be viewed for any day between October 2007
and today.<br/> We developed TorMap for our practical P4-Project at the <ExternalLink
and today.<br/> Initially TorMap was developed by us at the <ExternalLink
href={"https://www.tu-darmstadt.de/"} label={"Technical University of Darmstadt"}/> as part of
the <ExternalLink href={"https://panda-projekt.de/"} label={"PANDA project"}/>. It was
the <ExternalLink href={"https://panda-projekt.de/"} label={"PANDA project"}/>. The project was
supervised
by <ExternalLink href={"mailto:[email protected]"}
label={"Florian Platzer"}/> from <ExternalLink
Expand Down Expand Up @@ -141,6 +142,31 @@ export const AboutInformation: React.FunctionComponent = () => {
roles={["Server Admin"]}
/>
</Stack>
<h2>Donations</h2>
<Typography variant={"body1"} gutterBottom>
If you like our project and want to support us, you can donate to us via Bitcoin or other
cryptocurrencies. Your donation helps us to cover server costs and keep the project running.
</Typography>
<Stack
direction={{sm: 'column', md: 'row'}}
justifyContent="flex-start"
alignItems="flex-start"
spacing={{xs: 3, sm: 3}}
>
<BitcoinCard/>
<Card sx={{margin: 'auto', mt: 5, height: 128}}>
<CardContent sx={{paddingBottom: '0'}}>
<Typography gutterBottom variant="subtitle1" component="div">
Other cryptocurrencies
</Typography>
<a href={nowpaymentsDonationUrl} target="_blank" rel="noreferrer noopener">
<Button size="small" startIcon={<LaunchIcon/>}>
Donate via NOWPayments
</Button>
</a>
</CardContent>
</Card>
</Stack>
<Divider sx={{my: 2}}/>
<Typography variant={"body2"}>
We use IP geolocation data by <ExternalLink href={"https://db-ip.com"}
Expand Down
131 changes: 94 additions & 37 deletions frontend/src/util/layer-construction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import {Feature, GeoJsonObject, GeometryObject} from "geojson";
import L, {circleMarker, GeoJSON, HeatLatLngTuple, Layer, LayerGroup, LeafletMouseEvent, PathOptions} from "leaflet";
import L, {
CircleMarker,
circleMarker,
GeoJSON,
HeatLatLngTuple,
Layer,
LayerGroup,
LeafletMouseEvent,
PathOptions
} from "leaflet";

import {Colors} from "../config";
import {RelayLocationDto} from "../dto/relay";
Expand All @@ -14,6 +23,7 @@ import {
sortFamilyCoordinatesMap
} from "./aggregate-relays";
import {getUniqueCountryColor} from "./geojson";
import {backend} from "./util";

/**
* Returns a Layer with markers with size relative to number of relays on a coordinate.
Expand Down Expand Up @@ -46,51 +56,98 @@ export const buildAggregatedCoordinatesLayer = (

/**
* Returns a Layer with markers for each relay.
* @param latLonMap - The LatLonMap
* @param relayCoordinatesMap - The LatLonMap
* @param singleColor - Whether all markers should have the same color
* @param onMarkerClick - Event handler for clicking on a marker
*/
export const buildRelayLayer = (
latLonMap: Map<string, RelayLocationDto[]>,
relayCoordinatesMap: Map<string, RelayLocationDto[]>,
singleColor: boolean,
onMarkerClick: (e: LeafletMouseEvent) => void,
): LayerGroup => {
const defaultLayer = new LayerGroup()
const exitLayer = new LayerGroup()
const guardLayer = new LayerGroup()
const defaultMarkerLayer = new LayerGroup([defaultLayer, guardLayer, exitLayer])
latLonMap.forEach((coordinate, key) => {
coordinate.forEach(relay => {
let color = Colors.Default
let layer = defaultLayer
switch (getRelayType(relay)) {
case RelayType.Exit: {
color = Colors.Exit
layer = exitLayer
break
}
case RelayType.Guard: {
color = Colors.Guard
layer = guardLayer
break
}
}
if (singleColor) color = "#989898"
const layer = new LayerGroup()
relayCoordinatesMap.forEach((relaysAtCoordinates, coordinatesKey) => {
if (relaysAtCoordinates.length == 0) return

circleMarker(
[relay.lat, relay.long],
{
radius: 1,
className: key,
color: color,
weight: 3,
},
)
.on("click", onMarkerClick)
.addTo(layer)
})
const {
mostImportantRelay,
marker
} = addRelayMarker(relaysAtCoordinates, layer, singleColor, coordinatesKey, onMarkerClick)

addRelayNicknameTooltip(relaysAtCoordinates, mostImportantRelay, marker, layer);
})
return defaultMarkerLayer
return layer
}

function addRelayMarker(relaysAtCoordinates: RelayLocationDto[], targetLayer: LayerGroup, singleColor: boolean, coordinatesKey: string, onMarkerClick: (e: LeafletMouseEvent) => void) {
let mostImportantRelay = relaysAtCoordinates[0]
for (const relay of relaysAtCoordinates) {
if (getRelayType(relay) === RelayType.Exit) {
mostImportantRelay = relay
break
} else if (getRelayType(relay) === RelayType.Guard) {
mostImportantRelay = relay
}
}
let color = Colors.Default
switch (getRelayType(mostImportantRelay)) {
case RelayType.Exit: {
color = Colors.Exit
break
}
case RelayType.Guard: {
color = Colors.Guard
break
}
}
if (singleColor) color = "#989898"

const marker = circleMarker(
[mostImportantRelay.lat, mostImportantRelay.long],
{
radius: 1,
className: coordinatesKey,
color: color,
weight: 3,
},
)
.on("click", onMarkerClick)
.addTo(targetLayer)
return {mostImportantRelay, marker};
}

function addRelayNicknameTooltip(relays: RelayLocationDto[], mostImportantRelay: RelayLocationDto, marker: CircleMarker, targetLayer: LayerGroup) {
const knownRelayDetailIds: number[] = relays.map(relay => relay.detailsId)
.filter(id => id !== undefined && id !== null) as number[]

// Create an invisible larger circle around the marker
const hoverRadius = 15;
const hoverMarker = circleMarker(
[mostImportantRelay.lat, mostImportantRelay.long],
{
radius: hoverRadius,
fillOpacity: 0,
fill: false,
stroke: false,
},
)
.on('mouseover', async function (this: L.Marker, e) {
let tooltip = `${relays.length} relays`
const maxNicknamesShownFully = 3
if (knownRelayDetailIds.length <= maxNicknamesShownFully) {
await backend.post<string[]>('/relay/details/relay/nicknames', knownRelayDetailIds).then(response => {
tooltip = response.data.join(", ")
})
}
marker.bindTooltip(tooltip, {permanent: true, direction: 'top'}).openTooltip();
})
.on('mouseout', function (this: L.Marker, e) {
marker.unbindTooltip();
})
.on("click", function (this: L.Marker, e) {
marker.fire('click'); // Trigger the click event of the actual marker
})
.addTo(targetLayer)
}

/**
Expand Down

0 comments on commit dbb84f6

Please sign in to comment.