Initial version

This commit is contained in:
Felix Klenner 2024-07-18 17:55:28 +02:00
parent cc103b2337
commit 6874e3a0ce
10 changed files with 1031 additions and 2 deletions

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,3 +1,35 @@
# ip.zuim.de
# [ip.zuim.de](https://ip.zuim.de)
Shows information on connectivity of a user, by testing both IPv4 and IPv6. Also shows geoip data.
Shows information on connectivity of a user, by testing both IPv4 and IPv6. Also shows geoip data.
### Warning:
This code is mostly unmaintained and uses several old javascript libraries.
### Setup:
To get the geoip data, the geoip2 module for nginx is used with the following configuration:
```
geoip2 /var/lib/GeoIP/GeoLite2-City.mmdb {
auto_reload 1h;
$geoip2_data_continent_code continent code;
$geoip2_data_continent_name continent names en;
$geoip2_data_country_code country iso_code;
$geoip2_data_country_name country names en;
$geoip2_data_subdivision subdivisions 0 names en;
$geoip2_data_city_name city names en;
$geoip2_data_postal_code postal code;
$geoip2_data_location_time location time_zone;
}
geoip2 /var/lib/GeoIP/GeoLite2-ASN.mmdb {
auto_reload 1h;
$geoip2_data_asn autonomous_system_organization;
}
```
Download these files directly and add them in the main folder:
- https://ip.zuim.de/0B.zip
- https://ip.zuim.de/1MB.zip
- https://ip.zuim.de/10MB.zip
- https://ip.zuim.de/100MB.zip
- https://ip.zuim.de/img.jpg?t=1721317515702

10
include/advip.php Normal file
View file

@ -0,0 +1,10 @@
<?php
$ip = str_replace("::ffff:", "", $_SERVER['REMOTE_ADDR']);
$data = array();
$data["Hostname"] = gethostbyaddr($ip);
if($data["Hostname"]===$ip)
$data["Hostname"] = "";
echo json_encode($data);
?>

10
include/config.php Normal file
View file

@ -0,0 +1,10 @@
<?php
$config = array(
"ipv4" => "89.58.44.51",
"ipv6" => "2a03:4000:67:e03::1",
"nameDS" => "ip.zuim.de",
"namev4" => "v4.ip.zuim.de", # only use an A record for this domain
"namev6" => "v6.ip.zuim.de", # only use an AAAA record for this domain
"app_path" => "/",
);
?>

52
include/ipinfo.php Normal file
View file

@ -0,0 +1,52 @@
<?php
require_once("config.php");
$ip = array();
$ip["host"] = rtrim($_SERVER['HTTP_HOST'], '.');
$ip["ip"] = str_replace("::ffff:", "", $_SERVER['REMOTE_ADDR']);
$ip["proxyIp"] = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : "";
//$ip["clientIp"] = isset($_SERVER['HTTP_CLIENT_IP']) ? $_SERVER['HTTP_CLIENT_IP'] : "";
switch($ip["host"])
{
case $config["namev4"]:
case $config["ipv4"]:
$ip["name"] = "IPv4";
$ip["conHostType"] = 0;
break;
case $config["nameDS"]:
$ip["name"] = "Dualstack";
$ip["conHostType"] = 1;
break;
case $config["namev6"]:
case "[".$config["ipv6"]."]":
$ip["name"] = "IPv6";
$ip["conHostType"] = 2;
break;
default:
$ip["name"] = "unknown";
$ip["conHostType"] = -1;
}
if(strpos($ip["ip"],".") !== FALSE){
$ip["conClientType"] = 0;
}
else if(strpos($ip["ip"],":") !== FALSE){
$ip["conClientType"] = 2;
}
else{
$ip["conClientType"] = -1;
}
$ip["geoIp"] = array();
$ip["geoIp"]["Land"] = $_SERVER["GEOIP_COUNTRY_NAME"];
$ip["geoIp"]["Bundesland"] = $_SERVER["GEOIP_SUBDIVISION"];
$ip["geoIp"]["Stadt"] = $_SERVER["GEOIP_CITY_NAME"];
$ip["geoIp"]["PLZ"] = $_SERVER["GEOIP_POSTAL_CODE"];
$ip["geoIp"]["ISP"] = $_SERVER["GEOIP_ASN"];
if(!isset($NO_JSON_OUT)) {
echo json_encode($ip);
}
?>

135
index.php Executable file
View file

@ -0,0 +1,135 @@
<?php
$NO_JSON_OUT = 1; //include mode for ipinfo.php
require_once("include/config.php");
require_once("include/ipinfo.php");
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Netzwerktest (<?php echo $ip["name"]; ?>)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#e0e8ff">
<meta name="theme-color" content="#b7eeff">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="cont">
<h1><a class="title" href="//<?php echo $ip["host"].$config["app_path"]; ?>">
<?php echo $ip["name"]; ?> - Testseite
</a></h1>
<hr>
<div class="ipTest">
<div class="ip_v4"><div class="info_ip">Warte auf IPv4 <div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div></div><div class="info_geo"></div></div>
<hr style="margin-top:0;margin-bottom:0;">
<div class="ip_v6"><div class="info_ip">Warte auf IPv6 <div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div></div><div class="info_geo"></div></div>
<div class="ip_ds" style="display:none;"><div class="info_ip"></div><div class="info_geo"></div></div>
</div>
<hr>
<h3>Informationen & Downloads</h3>
<div id="resultExplanation"></div>
<?php
if($ip["conHostType"] == 1)
{
if($ip["conClientType"]==0)
{
echo "Dein Browser hat <b>IPv4</b> ausgewählt, um sich zu verbinden!<br>";
}
else
{
echo "Dein Browser hat <b>IPv6</b> ausgewählt, um sich zu verbinden!<br>";
}
}
else {
echo "Wenn diese Seite angezeigt wird unterstützt der Client <b>".$ip["name"]."</b>!<br>";
}
if(isset($_SERVER['HTTPS']) && "on" == $_SERVER['HTTPS'])
{
echo "Du bist mit <b>https</b> verbunden.";
}
else
{
echo "Du bist mit <b>http</b> verbunden.";
}
?>
<p>Download mit <?php echo $ip["name"]; ?>:
<a href="1MB.zip">1MB Download</a>,
<a href="10MB.zip">10MB Download</a>,
<a href="100MB.zip">100MB Download</a></p>
<p>Weitere Informationen:
<a href="//<?php echo $config["nameDS"]; ?>/raw/" target="_blank">Nur IP Adresse als Text</a>, &nbsp;
<a href="//status.zuim.de/" target="_blank">Server Statusseite</a>, &nbsp;
<a href="//zuim.de/" target="_blank">zuim.de Hauptseite </a></p>
<hr>
<h3 class='headCont'>
Rufe diese Seite direkt über
<a href="https://<?php echo $config["namev4"] . $config["app_path"]; ?>"> IPV4</a>
<a href="http://<?php echo $config["ipv4"] . $config["app_path"]; ?>" class="secondary_link">(Ohne DNS)</a> &nbsp;
<a href="https://<?php echo $config["nameDS"] . $config["app_path"]; ?>">Dualstack</a> &nbsp;
<a href="http://<?php echo $config["namev6"] . $config["app_path"]; ?>"> IPV6</a>
<a href="http://[<?php echo $config["ipv6"]."]" . $config["app_path"]; ?>" class="secondary_link">(Ohne DNS)</a>
auf.</h3>
<noscript>Aktiviere Javascript, für mehr Informationen:<br>IP: <?php echo $ip["ip"]; ?></noscript>
<hr>
<h3>Ping mit Graph messen: <button class="start">Start</button></h3>
<div class="out"></div>
<div class="ct-chart ct-octave" style="display: none;"></div>
<hr>
<h3>Andere Tests für Geschwindigkeit & Ping:</h3>
<a href="//craftandbuild.de/speedtest/speedtest_ajax">Speedtest mit ajax</a> &nbsp;
<a href="//craftandbuild.de/speedtest/speedtest_websocket">Speedtest mit websocket</a> &nbsp;
<a href="//craftandbuild.de/WebSockets/Pingtest">Pingtest mit websocket</a>
<a href="/librespeed/">LibreSpeed Instanz</a>
<button class="ping"><?php echo $ip["name"]; ?> Ping</button><br><span class="pingresult"></span>
<hr>
<h3>Bild(14MB) parallel mit IPv4 und IPv6 laden: <button class="speed">Start</button></h3>
<div class="images">
<div class="v4">
<h3>IPv4</h3>
<img class="testimg" src="" alt="v4 Test"/>
<p></p>
</div>
<div class="v6">
<h3>IPv6</h3>
<img class="testimg" src="" alt="v6 Test"/>
<p></p>
</div>
<br style="clear:both;">
<h3 class="ausg"></h3>
</div>
<br>
</div>
<!-- libs -->
<script src="//cdn.zuim.de/js/jQuery/jquery.js"></script>
<script defer src="//cdn.zuim.de/js/socket.io-4.1.2.js"></script>
<script defer src="//cdn.zuim.de/js/chartist-js-0.11.4/chartist.min.js"></script>
<script defer src="//cdn.zuim.de/js/chartist-js-0.11.4/chartist-plugin-axistitle.min.js"></script>
<script defer src="//cdn.zuim.de/js/chartist-js-0.11.4/chartist-plugin-legend.js"></script>
<link rel="stylesheet" href="//cdn.zuim.de/css/chartist.min.css">
<!-- scripts -->
<script>
var config = <?php echo json_encode($config); ?>;
var ipInfo_php = <?php echo json_encode($ip); ?>;
</script>
<script src="script.js"></script>
</body>
</html>

3
raw/index.php Executable file
View file

@ -0,0 +1,3 @@
<?php
echo str_replace("::ffff:", "", $_SERVER['REMOTE_ADDR'])."\n";
?>

7
robots.txt Normal file
View file

@ -0,0 +1,7 @@
User-agent: *
Disallow: 100KB.zip
Disallow: 500KB.zip
Disallow: 1MB.zip
Disallow: 10MB.zip
Disallow: 100MB.zip
Disallow: 1GB.zip

509
script.js Normal file
View file

@ -0,0 +1,509 @@
function copyBtn(className, btn){
var ele = $("."+className);
highlight(ele[0]);
document.execCommand('copy');
$(btn).css("backgroundColor","#0c0").delay(500).queue(function() {
$(btn).css("backgroundColor","");
$(btn).dequeue();
});
console.log("copied: "+ele.html());
}
function highlight(ele){
var range, selection;
if (document.body.createTextRange)
{
range = document.body.createTextRange();
range.moveToElementText(ele);
range.select();
} else if (window.getSelection)
{
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(ele);
selection.removeAllRanges();
selection.addRange(range);
}
}
$(document).ready(function()
{
var v4Div = $(".ip_v4");
var v6Div = $(".ip_v6");
var dsDiv = $(".ip_ds");
var testingResult = {};
function getIpInfo(url, div)
{
var infoDivIp = div.children(".info_ip");
var infoDivGeo = div.children(".info_geo");
var version = url.includes("4")?4:6
infoDivGeo.hide();
function getIpInfoDone(data) {
var className = "ip_"+url.replace(/\./g,"");
testingResult[version] = true;
if(testingResult[4] && testingResult[6]) {
$("#resultExplanation").html("Die Verbindung konnte über IPv4 und IPv6 hergestellt werden, also wird Dual-Stack unterstützt! Dies kann nativ, durch DS-Lite oder einen 6to4/4to6 Tunnel umgesetzt sein.<br><br>");
}
div.addClass("success");
infoDivIp.html("<h3>Deine <a href='//"+data.host+"/raw/'>"+data.name+" Adresse</a>: "+
"<span class='"+className+" ipAddr' onclick='highlight(this)'>"+data.ip+" </span> "+
"<button style='vertical-align: middle' onclick='copyBtn(\""+className+"\", this)'>Kopieren</button></h3>");
if(data.proxyIp != ""){
infoDivIp.append("<p>Vom Proxy angegebene Ip: "+data.proxyIp+" (HTTP_X_FORWARDED_FOR)</p>");
}
//if(data.clientIp != ""){
// infoDivIp.append("<p>Vom Client angegebene Ip: "+data.clientIp+" (HTTP_CLIENT_IP)</p>");
//}
for(var ele in data.geoIp)
{
if(data.geoIp[ele] != ""){
infoDivGeo.append("<span class='info'>"+ele+":</span>"+data.geoIp[ele]+"<br>");
}
}
infoDivGeo.fadeIn();
//start hostname request
$.ajax("//"+url+"/include/advip.php", {dataType: "json", timeout: 5000})
.done(function(data, status, xhr){
for(var ele in data)
{
if(data[ele] != ""){
infoDivGeo.prepend("<span class='info'>"+ele+":</span>"+data[ele]+"<br>");
}
}
}).fail(function(){
//fail geoip silently
});
}
if(ipInfo_php.conHostType==(div==v4Div?0:(div==v6Div?2:1))) {
console.log("Loaded over PHP: "+ipInfo_php.name);
getIpInfoDone(ipInfo_php);
}
else {
$.ajax("//"+url+"/include/ipinfo.php", {dataType: "json", timeout: 5000})
.done(function(data, status, xhr){
console.log("Loaded over Ajax: "+data.name);
getIpInfoDone(data);
}).fail(function(err){
testingResult[className] = false;
console.log(err);
div.addClass("failure");
infoDivIp.html("<span style='font-weight: bold;'>IPv"+version+" nicht verfügbar.</span>");
var ipWithoutDNS;
var name;
if(version == 4){
ipWithoutDNS = config["ipv4"];
}
else {
ipWithoutDNS = "["+config["ipv6"]+"]";
}
infoDivIp.append("<br>Versuche eine IPv"+version+" Adresse direkt aufzurufen, um auf DNS Fehler zu prüfen:"+
"<a href='http://"+ipWithoutDNS+"/'>"+ipWithoutDNS+"</a>");
});
}
}
getIpInfo(config.namev4, v4Div);
getIpInfo(config.namev6, v6Div);
//getIpInfo(config.nameDS, dsDiv);
var pingduration;
var pingcount;
var pingmax = 3000;
var pingInterval;
$(".ping").click(function()
{
$(".ping").prop('disabled', true);
pingduration = 0;
pingcount = 0;
ping();
setTimeout(function()
{
if(pingcount != -1)
{
$(".pingresult").html("Keine Antwort nach 5 Sekunden!");
}
$(".ping").prop('disabled', false);
},5000);
});
function ping()
{
var startTime = new Date().getTime();
var req = new XMLHttpRequest();
/*
0 UNSENT open()wurde noch nicht aufgerufen.
1 OPENED send()wurde noch nicht aufgerufen.
2 HEADERS_RECEIVED send() wurde aufgerufen, und Headers sowie Status sind verfügbar.
3 LOADING Download ist im Gange; responseText enthält bereits unvollständige Daten.
4 DONE Der Vorgang ist abgeschlossen.*/
req.onreadystatechange = function()
{
//console.log(req.readyState+" "+(new Date().getTime() - startTime));
if(req.readyState == 2 && pingcount >= 0)
{
var duration = new Date().getTime() - startTime;
pingduration += duration;
pingcount++;
$(".pingresult").html("Anzahl der Pings: "+pingcount+"<br>Zeit: "+Math.round(pingduration/pingcount * 10)/10 + "ms");
if(pingduration > pingmax)
{
$(".pingresult").append("<br>Fertig!");
pingcount = -1;
clearInterval(pingInterval);
}
else
{
ping();
}
}
};
req.open("GET", "/0B.zip?"+startTime);
req.send();
}
$(".speed").click(function()
{
var v4 = -1;
var v6 = -1;
var v4Speed = -1;
var v6Speed = -1;
var timeCount = 15;
$(this).hide();
$(".images").show();
time = new Date().getTime();
$(".v4 img").attr("src","//"+config.namev4+"/img.jpg?t="+time);
$(".v6 img").attr("src","//"+config.namev6+"/img.jpg?t="+time);
$(".v4 img, .v6 img").on("load", function()
{
dauer = (new Date().getTime()-time)/1000;
speed = Math.round((14*8)/dauer*100)/100;
$(this).next().html(speed + " Mbit/s für "+dauer + " Sekunden ");
//wert setzen
if($(this).parent().hasClass("v4"))
{
v4 = dauer;
v4Speed = speed;
}
if($(this).parent().hasClass("v6"))
{
v6 = dauer;
v6Speed = speed;
}
//fertig
fertig();
});
var intervall = setInterval(function(){timer()},100);
function timer()
{
//count
timeCount = (timeCount*10-1)/10;
//ausgabe
$(".ausg").html("Timeout: "+timeCount);
fertig();
}
function fertig()
{
if(timeCount <= 0 || v4 != -1 && v6 != -1)
{
clearInterval(intervall);
ausg = "";
win = -1;
loose = -1;
if(v4 == -1 && v6 == -1)
{
ausg = "Beide haben das laden nach Beenden des Timeout nicht abgeschlossen!";
win = 0;
loose = 0;
}
else if(v4 == -1)
{
ausg = "Nur IPv6 hat mit "+v6+" Sekunden funktioniert!";
win = 6;
loose = 4;
}
else if(v6 == -1)
{
ausg = "Nur IPv4 hat mit "+v4+" Sekunden funktioniert!";
win = 4;
loose = 6;
}
else if(v4 < v6)
{
ausg = "IPv4 war " + Math.round((v6/v4)*100)/100 + " mal schneller als IPv6";
win = 4;
loose = 6;
}
else if(v6 <= v4)
{
ausg = "IPv6 war " + Math.round((v4/v6)*100)/100 + " mal schneller als IPv4";
win = 6;
loose = 4;
}
$(".ausg").html("<h3>"+ausg+" <button onClick='location.reload();'> Seite erneut laden </button></h3>");
$(".v"+win).css("border", "5px solid #0a0");
$(".v"+loose).css("border", "5px solid #a00");
}
}
});
var active = false;
var id = -1; //ignore first measurement
var times4 = [];
var times6 = [];
var labels = [];
var startT;
$(".start").click(function() {
active = !active;
if(active) {
$(".start").html("Stop");
loadChart();
s4 = io('wss://'+config.namev4+'/', {path: '/speedtest'});
s6 = io('wss://'+config.namev6+'/', {path: '/speedtest'});
s4.on('pongDown', function(msg) {
times4[msg] = new Date().getTime() - times4[msg];
//console.log("Received via v4: " + msg + " Time:" + times4[msg]);
updateChart(msg, false);
});
s6.on('pongDown', function(msg) {
times6[msg] = new Date().getTime() - times6[msg];
//console.log("Received via v6: " + msg + " Time:" + times6[msg]);
updateChart(msg, true);
});
$(".out").html("Starte websocket<br>Wenn dies nicht funktioniert ist der NodeJS Server offline!");
$(".ct-chart").show();
$("html, body").scrollTop($(".ct-chart").offset().top);
}
else {
times4 = [];
times6 = [];
labels = [];
nTimes4 = [];
nTimes6 = [];
nLabels = [];
$(".start").html("Start");
}
});
setInterval(function() {
if(active) {
if(id >= 0) {
times4[id] = new Date().getTime();
times6[id] = new Date().getTime();
labels[id] = ""+(id/5);
}
s4.emit('pingUp', id);
s6.emit('pingUp', id);
id++;
}
}, 200);
function calcAvg()
{
var n4 = 0;
var avgAct4 = 0;
for(var i = 0; i < nTimes4.length; i++)
{
if(nTimes4[i]) {
avgAct4 += nTimes4[i];
n4++;
}
}
avgAct4 /= n4;
var n6 = 0;
var avgAct6 = 0;
for(var i = 0; i < nTimes6.length; i++)
{
if(nTimes6[i]) {
avgAct6 += nTimes6[i];
n6++;
}
}
avgAct6 /= n6;
$(".out").html("Ping Durchschnitt: IPv4: "+avgAct4.toFixed(1)+" ms &nbsp; / &nbsp; IPv6: "+avgAct6.toFixed(1)+" ms (Anfragen: "+n4+")");
}
var nTimes4 = [];
var nTimes6 = [];
var nLabels = [];
var chart;
function loadChart()
{
//trim data
/*var n = 50;
var start = 0>times4.length-n?0:times4.length-n;
for(var i = start; i < times4.length; i++)
{
if(times4[i] > 100*1000)
break;
nTimes4[i-start] = times4[i];
nLabels[i-start] = labels[i];
}
var n = 50;
var start = 0>times6.length-n?0:times6.length-n;
for(var i = start; i < times6.length; i++)
{
if(times6[i] > 100*1000)
break;
nTimes6[i-start] = times6[i];
nLabels[i-start] = labels[i];
}
*/
calcAvg(nTimes4);
calcAvg(nTimes6);
//draw chart
var options =
{
low: 0,
axisX:
{
labelInterpolationFnc: function skipLabels(value, index)
{
return index % 10 === 0 ? value : null;
}
},
chartPadding:
{
top: 50,
right: 5,
bottom: 20,
left: 25
},
plugins: [
Chartist.plugins.legend({
clickable: false,
legendNames: ['IPv4 Ping', 'IPv6 Ping']
}),
Chartist.plugins.ctAxisTitle(
{
axisX: {
axisTitle: 'Zeit in Sekunden',
axisClass: 'ct-axis-title',
offset: {
x: 0,
y: 35
},
textAnchor: 'middle'
},
axisY: {
axisTitle: 'Ping in ms',
axisClass: 'ct-axis-title',
offset: {
x: 0,
y: -10
},
textAnchor: 'middle',
flipTitle: false
}
})
]
}
chart = new Chartist.Line('.ct-chart', {labels: nLabels, series: [nTimes4,nTimes6]}, options);
console.log(chart);
}
var idToIndex = [];
function updateChart(id, v6) {
if(id < 0) {
return;
}
var limit = 50;
if(nLabels.length >= limit) {
nLabels.shift();
}
if(nTimes4.length >= limit) {
nTimes4.shift();
}
if(nTimes6.length >= limit) {
nTimes6.shift();
}
if(!nLabels.includes(labels[id])) {
nLabels.push(labels[id]);
//set other value to null while waiting
if(v6) {
nTimes6.push(times6[id]);
nTimes4.push(null);
} else {
nTimes4.push(times4[id]);
nTimes6.push(null);
}
}
else{
//replace previously inserted null value
if(v6) {
nTimes6[nTimes6.length-1] = times6[id];
} else {
nTimes4[nTimes4.length-1] = times4[id];
}
}
calcAvg(nTimes4);
calcAvg(nTimes6);
chart.update({labels: nLabels, series: [nTimes4,nTimes6]});
}
});

265
style.css Normal file
View file

@ -0,0 +1,265 @@
*
{
font-family: Arial;
}
h4,h3,h5,h6
{
margin: 5px 0;
}
.v4, .v6
{
float:left;
width: 45%;
border: 3px solid black;
border-radius:10px;
margin: 5px;
padding: 0;
overflow:hidden;
}
.v4 p, .v4 h3, .v6 p, .v6 h3
{
margin: 0 5px;
}
.images
{
display:none;
}
.testimg
{
height: 400px;
}
html,body
{
margin: 0;
padding: 0;
background-color: #1e1e1e;
}
/*Hauptcontainer*/
.cont
{
position: absolute;
left: 0;
right:0;
width: 100%;
max-width: 1000px;
min-height: 100%;
margin: -10px auto -10px auto;
background: #f0f0f0;
border: 1px solid #999;
border-width: 0 1px;
overflow-x: hidden;
overflow-y: visible;
padding: 10px 10px 0;
}
.cont h1
{
background: #4B87E7;
width: 200%;
margin-left: -10px;
padding: 10px;
color: #e7e7e7;
}
.headCont
{
padding: unset;
}
.headCont a
{
margin:3px;
padding: 5px;
}
hr
{
width: 200%;
margin: 10px 0 10px -30px;
height: 3px;
background: rgba(0,0,0,0.4);
border: none;
box-shadow: none;
}
span.info
{
display:inline-block;
font-weight:bold;
width:105px;
}
a
{
text-decoration: none;
color: #4B87E7;
border-radius: 3px;
padding: 0 2px;
transition: all 0.2s;
}
a.title
{
color: #eaeaea;
}
a:hover
{
background: #4B87E7;
color: #f5f5f5;
}
a.title:hover
{
color: #111;
}
.secondary_link
{
font-size:70%;
vertical-align:top;
}
.ipAddr {
font-weight:normal;
cursor:pointer;
}
.ipTest{
margin: -10px;
}
.ipTest > div{
padding: 10px;
transition: all 0.4s;
background-color: rgba(0, 0, 0, 0);
}
.ipTest .success {
background-color: rgb(220, 255, 160);
}
.ipTest .failure {
background-color: rgba(255, 100, 136, 0.46);
}
.info_geo{
transition: all 0.3s;
padding: 7px;
margin: -7px;
border-radius: 10px;
display: inline-block;
}
.info_geo:hover{
background: rgba(255,255,255,0.8);
box-shadow: inset 0 0 10px black;
}
/* loading icon */
.lds-ellipsis {
margin-left: 20px;
display: inline-block;
position: relative;
width: 40px;
height: 11px;
}
.lds-ellipsis div {
position: absolute;
width: 13px;
height: 13px;
border-radius: 50%;
background: #fff;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
box-shadow: 0 0 5px black;
}
.lds-ellipsis div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
/*chart*/
/*v4*/
.ct-series-a .ct-line,
.ct-legend .ct-series-0:before {
stroke: #363880 !important;
background-color: #363880 !important;
border-color: #363880 !important;
}
.ct-series-a .ct-point
{
fill: #363880 !important;
stroke: #363880 !important;
}
/*v6*/
.ct-series-b .ct-line,
.ct-legend .ct-series-1:before {
stroke: #FA9100 !important;
background-color: #FA9100 !important;
border-color: #FA9100 !important;
}
.ct-series-b .ct-point
{
fill: #FA9100 !important;
stroke: #FA9100 !important;
}
/*Legende*/
.ct-legend {
background: rgba(255,255,255,0.7);
padding: 3px;
border-radius: 5px;
position: absolute;
right:10px;
top: 10px;
z-index: 10;
list-style: none;
}
.ct-legend li {
position: relative;
padding-left: 23px;
margin-bottom: 3px;
}
.ct-legend li:before {
width: 12px;
height: 12px;
position: absolute;
left: 0;
content: '';
border: 3px solid transparent;
border-radius: 2px;
}
.ct-legend li.inactive:before {
background: transparent;
}
.ct-legend.ct-legend-inside {
position: absolute;
top: 0;
right: 0;
}