Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nicknames and donation information #262

Merged
merged 3 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading