123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>EdgeVPN</title>
- <meta name="description" content="Edgevpn dashboard">
- <meta name="keywords" content="edgevpn,dashboard">
- <script src="/js/apexcharts.min.js"></script>
- <script src="/js/alpine-magic-helpers.min.js" defer></script>
- <script src="/js/alpine.min.js" defer></script>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
- <style>
- .bg-black-alt {
- background:#191919;
- }
- .text-black-alt {
- color:#191919;
- }
- .border-black-alt {
- border-color: #191919;
- }
- .string { color: green; }
- .number { color: darkorange; }
- .boolean { color: blue; }
- .null { color: magenta; }
- .key { color: red; }
-
- #checkbox:checked + label .switch-ball{
- background-color: white;
- transform: translateX(24px);
- transition: transform 0.3s linear;
- }
- </style>
- <script src="/js/tailwind.min.js"></script>
- <script>
- tailwind.config = {
- darkMode: 'class',
- theme: {
- extend: {
- colors: {
- clifford: '#da373d',
- }
- }
- }
- }
- </script>
- </head>
- <body class="font-sans leading-normal tracking-normal"
- x-data="{page: location.hash, 'darkMode': false }"
- @hashchange.window="page = location.hash"
- x-init="
- darkMode = JSON.parse(localStorage.getItem('darkMode'));
- $watch('darkMode', value => localStorage.setItem('darkMode', JSON.stringify(value)))"
- x-bind:class="darkMode === true ? 'dark bg-black-alt': 'bg-white'"
- >
- <nav id="header" class="bg-slate-100 dark:bg-gray-900 fixed w-full z-10 top-0 shadow">
-
- <div class="w-full container mx-auto flex flex-wrap items-center mt-0 pt-3 pb-3 md:pb-0">
-
- <div class="w-1/2 pl-2 md:pl-0 align-text-bottom">
- <a class="text-gray-100 text-base xl:text-xl no-underline hover:no-underline font-bold align-top" href="#">
- <img src="/images/logo.png" class="object-scale-down float-left h-7 w-7"> <span class="pl-4 pt-1 md:pb-0 text-md text-slate-700 dark:text-slate-100"> EdgeVPN </span>
- </a>
- </div>
- <div class="w-1/2 pr-0">
- <div class="flex relative inline-block float-right">
- <!-- Dark/Light mode button-->
- <div >
- <div>
- <div class="dark:text-gray-100">
- <div class="flex items-center justify-center space-x-2">
- <span class="text-sm text-gray-800 dark:text-gray-500 p-2">Light</span>
- <label for="toggle"
- class="flex items-center h-5 p-1 duration-300 ease-in-out bg-gray-300 rounded-full cursor-pointer w-9 dark:bg-gray-600">
- <div
- class="w-4 h-4 duration-300 ease-in-out transform bg-white rounded-full shadow-md toggle-dot dark:translate-x-3">
- </div>
- </label>
- <span class="text-sm text-gray-400 dark:text-white p-2">Dark</span>
- <input id="toggle" type="checkbox" class="hidden" :value="darkMode" @change="darkMode = !darkMode" />
- </div>
- </div>
- </div>
- </div>
- <!-- END Dark/Light mode button-->
- <div class="block lg:hidden pr-4">
- <button id="nav-toggle" class="flex items-center px-3 py-2 border rounded text-gray-500 border-gray-600 hover:text-gray-100 hover:border-teal-500 appearance-none focus:outline-none">
- <svg class="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>
- </button>
- </div>
- </div>
- </div>
- <div class="w-full flex-grow lg:flex lg:items-center lg:w-auto hidden lg:block mt-2 lg:mt-0 bg-white-300 dark:bg-gray-900 z-20" id="nav-content">
- <ul class="list-reset lg:flex flex-1 items-center px-4 md:px-0">
- {{ $opts:= dict "name" "Home" "page" "" "icon" "fa-home" }}
- {{ template "menu_entry" $opts}}
- {{ $opts:= dict "name" "Nodes" "page" "#nodes" "icon" "fa-server" }}
- {{ template "menu_entry" $opts}}
- {{ $opts:= dict "name" "DNS" "page" "#dns" "icon" "fa-globe" }}
- {{ template "menu_entry" $opts}}
- {{ $opts:= dict "name" "Blockchain" "page" "#blockchain" "icon" "fa-dice-d20" }}
- {{ template "menu_entry" $opts}}
- {{ $opts:= dict "name" "Services" "page" "#services" "icon" "fa-ethernet" }}
- {{ template "menu_entry" $opts}}
- {{ $opts:= dict "name" "Peers" "page" "#peers" "icon" "fa-users" }}
- {{ template "menu_entry" $opts}}
- </ul>
-
- <div class="relative pull-right pl-4 pr-4 md:pr-0">
- </div>
- </div>
- </div>
- </nav>
- <!-- Nodes Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === '#nodes'">
- {{ $opts:= dict "text" "Machines API Documentation " "url" "https://mudler.github.io/edgevpn/docs/getting-started/api/#apimachines"}}
- {{ template "readme_badge" $opts}}
- {{ $opts:= dict "delete" "d.Address" "condition" "x-bind:class=\"d.Online ? 'bg-lime-100 dark:bg-lime-800' : 'bg-stone-200 dark:bg-stone-800'\"" "title" "Nodes" "func" "machines" "fields" ( list "Address" "PeerID" "Hostname" "OS" "Architecture" "Version") "struct" (list "d.Address" "d.PeerID" "d.Hostname" "d.OS" "d.Arch" "d.Version")}}
- {{ template "table" $opts}}
- </div>
- <!--END Node Container-->
- <!-- Blockchain Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === '#blockchain'">
- <div class="w-full px-4 md:px-0 md:mt-8 mb-16 text-gray-800 leading-normal">
- <div class="w-full mt-12 p-3 dark:bg-gray-900 bg-white-100 border dark:border-gray-800 rounded shadow"
- x-data="blockchain()"
- x-init="$interval(updateItems, 5500)"
- >
- <div class="border-b border-gray-800 p-3">
- <h5 class="font-bold uppercase text-gray-600">Blockchain </h5>
- <h5 class="font-bold uppercase text-gray-600">
- <span class="mt-1 text-xs px-2 py-1 font-semibold leading-tight text-slate-700 bg-slate-100 rounded-full dark:bg-slate-700 dark:text-slate-100"><i class="fa-solid fa-hashtag fa-fw mr-3"></i> <span x-text="blockchain.Index"></span> </span>
- <span class="mt-1 text-xs px-2 py-1 font-semibold leading-tight text-slate-700 bg-slate-100 rounded-full dark:bg-slate-700 dark:text-slate-100"><i class="fa-solid fa-clock fa-fw mr-3"></i> <span x-text="blockchain.Timestamp"></span> </span>
- <span class="mt-1 text-xs px-2 py-1 font-semibold leading-tight text-slate-700 bg-slate-100 rounded-full dark:bg-slate-700 dark:text-slate-100"><i class="fa-solid fa-dice-d20 fa-fw mr-3"></i> <span x-text="blockchain.Hash"></span> </span>
- <span class="mt-1 text-xs px-2 py-1 font-semibold leading-tight text-slate-700 bg-slate-100 rounded-full dark:bg-slate-700 dark:text-slate-100"><i class="fa-solid fa-link fa-fw mr-3"></i> <span x-text="blockchain.PrevHash"></span> </span>
- </div>
- <section class="flex justify-center mt-10">
-
- <div class="bg-white-300 py-1 w-11/12 rounded border-b-4 border-red-400 flex dark:text-white">
- <pre class="overflow-auto"><code class="overflow-auto" x-html="syntaxHighlight(JSON.stringify(blockchain, null, 2))"></code></pre>
- </div>
- </section>
- </div>
- </div>
- </div>
- <!--END Blockchain Container-->
- <!-- DNS Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === '#dns'">
- {{ $opts:= dict "text" "DNS Documentation " "url" "https://mudler.github.io/edgevpn/docs/concepts/overview/dns/"}}
- {{ template "readme_badge" $opts}}
- {{ $opts:= dict "delete" "d.Regex" "title" "DNS" "func" "dns" "fields" ( list "Regex" "Records") "struct" (list "d.Regex" "JSON.stringify(d.Records)")}}
- {{ template "table" $opts}}
- </div>
- <!--END DNS Container-->
- <!-- Services Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === '#services'">
- {{ $opts:= dict "text" "Tunnelling Documentation " "url" "https://mudler.github.io/edgevpn/docs/concepts/overview/services/"}}
- {{ template "readme_badge" $opts}}
- {{ $opts:= dict "title" "TCP Tunnels" "func" "services" "fields" ( list "Name" "PeerID") "struct" (list "d.Name" "d.PeerID")}}
- {{ template "table" $opts}}
- <hr class="border-b-2 border-gray-600 my-8 mx-4">
- {{ $opts:= dict "text" "Files Documentation " "url" "https://mudler.github.io/edgevpn/docs/concepts/overview/files/"}}
- {{ template "readme_badge" $opts}}
- {{ $opts:= dict "title" "Files" "func" "files" "fields" ( list "Name" "PeerID") "struct" (list "d.Name" "d.PeerID")}}
- {{ template "table" $opts}}
- </div>
- <!--END Services Container-->
- <!-- Peers Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === '#peers'">
- {{ $opts:= dict "title" "Nodes" "func" "nodes" "fields" ( list "PeerID" "Online" ) "struct" (list "d.ID" "d.Online")}}
- {{ template "table" $opts}}
- <hr class="border-b-2 border-gray-600 my-8 mx-4">
- {{ $opts:= dict "title" "Peer store" "func" "peerstore" "fields" ( list "PeerID" ) "struct" (list "d.ID")}}
- {{ template "table" $opts}}
- </div>
- </div>
- <!--END Peers Container-->
- <!-- Index Container-->
- <div class="container w-full mx-auto pt-20" x-show="page === ''">
-
- <div class="w-full px-4 md:px-0 md:mt-8 mb-16 text-gray-800 leading-normal"
- x-data="summary()"
- x-init="$interval(updateItems, 1500); initChart()"
- >
- <!--Summary Content-->
-
- <div class="flex flex-wrap">
- {{ $opts:= dict "name" "VPN Nodes" "color" "bg-emerald-600" "icon" "fas fa-network-wired" "field" "summary.Machines"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "P2P peers" "color" "bg-cyan-600" "icon" "fa-solid fa-circle-nodes" "field" "summary.Peers"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "Files" "color" "bg-yellow-600" "icon" "fa-solid fa-box-archive" "field" "summary.Files"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "Users" "color" "bg-pink-600" "icon" "fas fa-users" "field" "summary.Users"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "Blockchain index" "color" "bg-indigo-600" "icon" "fas fa-dice-d20" "field" "summary.BlockChain"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "Services" "color" "bg-sky-600" "icon" "fa-solid fa-car-tunnel" "field" "summary.Services"}}
- {{ template "metric_card" $opts}}
- {{ $opts:= dict "name" "Total downloaded" "color" "bg-cyan-600" "icon" "fas fa-download" "field" "bytesToSize(metrics.TotalIn)"}}
- {{ template "metric_card" $opts}}
- <div class="w-full md:w-1/2 xl:w-1/3 p-3 text-gray-800 ">
- <div class="dark:bg-gray-900 rounded shadow dark:border-gray-600 border-b-4 ">
- <div class="bg-white-200 dark:bg-gray-900 p-3">
- <h5 class="font-bold float-left uppercase text-gray-400">
- <span class="rounded p-1 bg-teal-600"><i class="fa-duotone fa-right-left fa-fw fa-inverse"></i></span>
- Bandwidth
- </h5>
- <h5 class="font-bold uppercase float-right text-gray-600">
- <span class="rounded p-1 bg-cyan-600"><i class="fas fa-arrow-down fa-fw fa-inverse"></i></span>
- <span x-text="bytesToSize(metrics.RateIn)"></span>
- <span class="rounded p-1 bg-amber-600"><i class="fas fa-arrow-up fa-fw fa-inverse"></i></span>
- <span x-text="bytesToSize(metrics.RateOut)"></span>
- </h5>
- </div>
- <br>
- <div class=" relative mt-1 ">
- <!-- Network stat Card-->
- <div class="dark:bg-gray-900 bg-white-100 rounded shadow p-2">
- <div class="flex flex-row items-center">
- <div class="flex-1 text-right md:text-center">
- <div x-ref="chart"></div>
- </div>
- </div>
- </div>
- <!--/Network stat Card-->
- </div>
- </div>
- </div>
- {{ $opts:= dict "name" "Total uploaded" "color" "bg-amber-600" "icon" "fas fa-upload" "field" "bytesToSize(metrics.TotalOut)"}}
- {{ template "metric_card" $opts}}
- <!--Divider-->
- <hr class="border-b-2 border-gray-600 my-8 mx-4">
- {{ $opts:= dict "title" "Connected users" "func" "users" "fields" ( list "PeerID" "Time") "struct" (list "d.PeerID" "d.Timestamp")}}
- {{ template "table" $opts}}
-
- <!--/ Summary Content-->
-
- </div>
- </div>
- <!--END Index /container-->
-
- <footer class="dark:bg-gray-900 border-t dark:border-gray-400 shadow">
- <div class="container max-w-md mx-auto flex py-8">
- <div class="w-full mx-auto flex flex-wrap">
- <div class="flex w-full md:w-1/2 ">
- <div class="px-8">
- <h3 class="font-bold font-bold dark:text-gray-100">About</h3>
- <p class="py-4 text-gray-600 text-sm">
- <strong>EdgeVPN</strong> by <a href="https://github.com/mudler/edgevpn">Ettore Di Giacinto</a>.<br>
- License <a href="https://github.com/mudler/edgevpn/blob/master/LICENSE">
- Apache v2</a>. <br>
- Logo originally made by <a href="https://www.flaticon.com/authors/uniconlabs" title="Uniconlabs">Uniconlabs</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
- </p>
- </div>
- </div>
-
- <div class="flex w-full md:w-1/2">
- <div class="px-8">
- <h3 class="font-bold font-bold dark:text-gray-100">Links</h3>
- <ul class="list-reset items-center text-sm pt-3">
- <li>
- <a class="inline-block text-gray-600 no-underline hover:text-black dark:hover:text-gray-100 hover:text-underline py-1" href="https://github.com/mudler/edgevpn" target=_blank><i class="fa-brands fa-github-alt fa-fw mr-3"></i>Github</a>
- </li>
- <li>
- <a class="inline-block text-gray-600 no-underline hover:text-black dark:hover:text-gray-100 hover:text-underline py-1" href="https://mudler.github.io/edgevpn/docs" target=_blank><i class="fas fa-book fa-fw mr-3"></i>Documentation</a>
- </li>
- <li>
- <a class="inline-block text-gray-600 no-underline hover:text-black dark:hover:text-gray-100 hover:text-underline py-1" href="https://github.com/mudler/edgevpn/issues/new" target=_blank><i class="fas fa-bug fa-fw mr-3"></i>Report issue</a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- </footer>
- <script>
- function bytesToSize(bytes, decimals = 2) {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- var s = sizes[i]
- if (!sizes[i]) {
- s = "B"
- }
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + s;
- }
- function scaleSize(bytes, scale = 1, decimals = 2) {
- if (bytes === 0) return '0';
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- //const i = Math.floor(Math.log(bytes) / Math.log(k));
- //console.log(i)
- return parseFloat((bytes / Math.pow(k, scale)).toFixed(dm));
- }
-
- const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
- function calcPages(n, total, size) {
- start = 1;
- if (n > 5 ){
- start = n - 5
- }
- end = Math.ceil(total / size);
- if (end - n > 5 ){
- end = n + 5
- // Math.ceil(this.total / this.size) - 10
- }
- return range(start, end ,1)
- }
- function filter(obj, key) {
- const start = obj.pageNumber * obj.size,
- end = start + obj.size;
- if (obj.search === "") {
- obj.total = obj.data.length;
- return obj.data.slice(start, end);
- }
- //Return the total results of the filters
- obj.total = obj.data.filter((item) => {
- return item[key]
- .toLowerCase()
- .includes(obj.search.toLowerCase());
- }).length;
- //Return the filtered data
- return obj.data
- .filter((item) => {
- return item[key]
- .toLowerCase()
- .includes(obj.search.toLowerCase());
- })
- .slice(start, end);
- }
- function endRes(obj) {
- let resultsOnPage = (obj.pageNumber + 1) * obj.size;
- if (resultsOnPage <= obj.total) {
- return resultsOnPage;
- }
- return obj.total;
- }
- function sortData(key, order = "asc") {
- return function innerSort(a, b) {
- if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
- return 0;
- }
- const varA = typeof a[key] === "string" ? a[key].toUpperCase() : a[key];
- const varB = typeof b[key] === "string" ? b[key].toUpperCase() : b[key];
- let comparison = 0;
- if (varA > varB) {
- comparison = 1;
- } else if (varA < varB) {
- comparison = -1;
- }
- return order === "desc" ? comparison * -1 : comparison;
- }
- }
- {{ $opts:= dict "endpoint" "machines" "func" "machines" "sort" "Address" "delete" "machines"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "peerstore" "func" "peerstore" "sort" "ID"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "nodes" "func" "nodes" "sort" "ID"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "users" "func" "users" "sort" "ID"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "dns" "func" "dns" "sort" "Regex" "delete" "dns"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "services" "func" "services" "sort" "Name"}}
- {{ template "table_js" $opts}}
- {{ $opts:= dict "endpoint" "files" "func" "files" "sort" "Name"}}
- {{ template "table_js" $opts}}
- function blockchain(){
- return {
- blockchain: {},
- updateItems() {
- fetch('/api/blockchain')
- .then(response => response.json())
- .then(data => this.blockchain = data )
- }
- };
- }
- function syntaxHighlight(json) {
- json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
- return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
- var cls = 'number';
- if (/^"/.test(match)) {
- if (/:$/.test(match)) {
- cls = 'key';
- } else {
- cls = 'string';
- }
- } else if (/true|false/.test(match)) {
- cls = 'boolean';
- } else if (/null/.test(match)) {
- cls = 'null';
- }
- return '<span class="' + cls + '">' + match + '</span>';
- });
- }
- function summary(){
- return {
- summary: {},
- chart: null,
- metrics: {},
- initChart() {
- this.chart = new ApexCharts(this.$refs.chart, {
- chart: {
- type: 'area',
- height: 80,
- sparkline: {
- enabled: true
- },
- dropShadow: {
- enabled: true,
- top: 1,
- left: 1,
- blur: 2,
- opacity: 0.2,
- }
- },
- dataLabels: {
- enabled: false,
- },
- series: [
- {
- name: "Download",
- data: [],
- },
- {
- name: "Upload",
- data: [],
- },
- ],
- stroke: {
- curve: 'smooth'
- },
- markers: {
- size: 0
- },
- grid: {
- padding: {
- top: 20,
- bottom: 10,
- }
- },
- colors: ['#247BA0', '#FF1654' ],
-
- noData: {
- text: "Loading...",
- },
- xaxis: {
- labels: {
- show: false,
- },
- },
- tooltip: {
- x: {
- show: false
- },
- y: {
- formatter: function(value, { series, seriesIndex, dataPointIndex, w }) {
- return value + ' KB/s'
- }
- }
- }
- })
- this.chart.render()
- },
- updateItems() {
- fetch('/api/summary')
- .then(response => response.json())
- .then(data => this.summary = data )
- fetch('/api/metrics')
- .then(response => response.json())
- .then(data => {
- this.metrics = data;
- this.chart.appendData([{ data: [ scaleSize(data.RateIn) ] }, { data: [ scaleSize(data.RateOut) ] } ]);
- } )
- }
- };
- }
- /*Toggle dropdown list*/
- /*https://gist.github.com/slavapas/593e8e50cf4cc16ac972afcbad4f70c8*/
- var navMenuDiv = document.getElementById("nav-content");
- var navMenu = document.getElementById("nav-toggle");
-
- document.onclick = check;
- function check(e){
- var target = (e && e.target) || (event && event.srcElement);
-
- //Nav Menu
- if (!checkParent(target, navMenuDiv)) {
- // click NOT on the menu
- if (checkParent(target, navMenu)) {
- // click on the link
- if (navMenuDiv.classList.contains("hidden")) {
- navMenuDiv.classList.remove("hidden");
- } else {navMenuDiv.classList.add("hidden");}
- } else {
- // click both outside link and outside menu, hide menu
- navMenuDiv.classList.add("hidden");
- }
- }
-
- }
- function checkParent(t, elm) {
- while(t.parentNode) {
- if( t == elm ) {return true;}
- t = t.parentNode;
- }
- return false;
- }
- </script>
- </body>
- </html>
|