done
This commit is contained in:
parent
feb08f617e
commit
f972004abc
@ -118,7 +118,8 @@
|
||||
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be admin/admin",
|
||||
"settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)",
|
||||
"settings_variable_desc_default_slide_duration": "Intro slide duration (in seconds)",
|
||||
"settings_variable_desc_default_slide_duration": "Introduction slide duration (in seconds)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide",
|
||||
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
|
||||
"settings_variable_desc_playlist_default_time_sync": "Sync slides across players for default playlist",
|
||||
|
||||
|
||||
@ -119,6 +119,7 @@
|
||||
"settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)",
|
||||
"settings_variable_desc_default_slide_duration": "Durée de la slide d'introduction (en secondes)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction",
|
||||
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
|
||||
"settings_variable_desc_playlist_default_time_sync": "Synchroniser les slides des lecteurs pour la liste de lecture par défaut",
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ from src.model.entity.Variable import Variable
|
||||
from src.model.enum.VariableType import VariableType
|
||||
from src.model.enum.HookType import HookType
|
||||
from src.model.hook.HookRegistration import HookRegistration
|
||||
from src.utils import am_i_in_docker
|
||||
from src.util.utils import am_i_in_docker
|
||||
|
||||
|
||||
class GitUpdater(ObPlugin):
|
||||
|
||||
@ -4,7 +4,7 @@ import platform
|
||||
from flask import Flask, redirect, url_for
|
||||
|
||||
from src.interface.ObController import ObController
|
||||
from src.utils import run_system_command, sudo_run_system_command, get_working_directory, am_i_in_docker
|
||||
from src.util.utils import run_system_command, sudo_run_system_command, get_working_directory, am_i_in_docker
|
||||
from src.Application import Application
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,8 @@ from flask import Flask, render_template, redirect, request, url_for, send_from_
|
||||
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.interface.ObController import ObController
|
||||
from src.utils import get_ip_address, get_safe_cron_descriptor
|
||||
from src.util.utils import get_safe_cron_descriptor
|
||||
from src.util.UtilNetwork import get_ip_address
|
||||
from src.model.enum.AnimationSpeed import animation_speed_duration
|
||||
|
||||
|
||||
@ -63,10 +64,10 @@ class PlayerController(ObController):
|
||||
)
|
||||
|
||||
def player_default(self):
|
||||
ipaddr = get_ip_address()
|
||||
return render_template(
|
||||
'player/default.jinja.html',
|
||||
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr')
|
||||
ipaddr=get_ip_address(),
|
||||
time_with_seconds=self._model_store.variable().get_one_by_name('default_slide_time_with_seconds')
|
||||
)
|
||||
|
||||
def player_playlist(self, playlist_slug_or_id: str = ''):
|
||||
|
||||
@ -8,7 +8,8 @@ from src.service.ModelStore import ModelStore
|
||||
from src.model.entity.Slide import Slide
|
||||
from src.model.enum.SlideType import SlideType
|
||||
from src.interface.ObController import ObController
|
||||
from src.utils import str_to_enum, get_optional_string, randomize_filename
|
||||
from src.util.utils import str_to_enum, get_optional_string
|
||||
from src.util.UtilFile import randomize_filename
|
||||
|
||||
|
||||
class SlideshowController(ObController):
|
||||
|
||||
@ -11,7 +11,8 @@ from src.manager.ConfigManager import ConfigManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
|
||||
from src.interface.ObController import ObController
|
||||
from src.utils import get_ip_address, am_i_in_docker
|
||||
from src.util.utils import am_i_in_docker
|
||||
from src.util.UtilNetwork import get_ip_address
|
||||
from src.service.Sysinfo import get_all_sysinfo
|
||||
|
||||
|
||||
@ -21,6 +22,7 @@ class SysinfoController(ObController):
|
||||
self._app.add_url_rule('/sysinfo', 'sysinfo_attribute_list', self._auth(self.sysinfo), methods=['GET'])
|
||||
self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self.sysinfo_restart, methods=['GET', 'POST'])
|
||||
self._app.add_url_rule('/sysinfo/restart/needed', 'sysinfo_restart_needed', self._auth(self.sysinfo_restart_needed), methods=['GET'])
|
||||
self._app.add_url_rule('/sysinfo/get/ipaddr', 'sysinfo_get_ipaddr', self._auth(self.sysinfo_get_ipaddr), methods=['GET'])
|
||||
|
||||
def sysinfo(self):
|
||||
return render_template(
|
||||
@ -70,3 +72,7 @@ class SysinfoController(ObController):
|
||||
pass
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
def sysinfo_get_ipaddr(self):
|
||||
ipaddr = get_ip_address()
|
||||
return ipaddr if ipaddr else ''
|
||||
|
||||
@ -5,7 +5,7 @@ import logging
|
||||
import argparse
|
||||
|
||||
from src.manager.VariableManager import VariableManager
|
||||
from src.utils import am_i_in_docker
|
||||
from src.util.utils import am_i_in_docker
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import sqlite3
|
||||
import logging
|
||||
|
||||
from sqlite3 import Cursor
|
||||
from src.utils import wrap_if, is_wrapped_by
|
||||
from src.util.utils import wrap_if, is_wrapped_by
|
||||
from typing import Optional, Dict
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import logging
|
||||
from typing import Union, Dict
|
||||
from enum import Enum
|
||||
|
||||
from src.utils import camel_to_snake
|
||||
from src.util.utils import camel_to_snake
|
||||
|
||||
|
||||
class LangManager:
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
|
||||
from src.model.entity.Playlist import Playlist
|
||||
from src.utils import get_optional_string, get_yt_video_id, slugify
|
||||
from src.util.utils import get_optional_string, get_yt_video_id, slugify
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
from src.manager.LangManager import LangManager
|
||||
from src.manager.UserManager import UserManager
|
||||
|
||||
@ -5,7 +5,7 @@ from typing import Dict, Optional, List, Tuple, Union
|
||||
from src.model.entity.Slide import Slide
|
||||
from src.model.entity.Playlist import Playlist
|
||||
from src.model.enum.SlideType import SlideType
|
||||
from src.utils import get_optional_string, get_yt_video_id
|
||||
from src.util.utils import get_optional_string, get_yt_video_id
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
from src.manager.LangManager import LangManager
|
||||
from src.manager.UserManager import UserManager
|
||||
|
||||
@ -14,7 +14,7 @@ from src.model.enum.VariableSection import VariableSection
|
||||
from src.model.enum.AnimationEntranceEffect import AnimationEntranceEffect
|
||||
from src.model.enum.AnimationExitEffect import AnimationExitEffect
|
||||
from src.model.enum.AnimationSpeed import AnimationSpeed
|
||||
from src.utils import get_keys, enum_to_str, enum_to_dict
|
||||
from src.util.utils import get_keys, enum_to_str, enum_to_dict
|
||||
|
||||
SELECTABLE_BOOLEAN = {"1": "✅", "0": "❌"}
|
||||
|
||||
@ -111,6 +111,7 @@ class VariableManager:
|
||||
|
||||
### Player Options
|
||||
{"name": "default_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_default_slide_duration'), "refresh_player": False},
|
||||
{"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False},
|
||||
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
|
||||
|
||||
### Player Animation
|
||||
|
||||
@ -3,7 +3,7 @@ import time
|
||||
|
||||
from typing import Optional, Union
|
||||
from src.model.enum.SlideType import SlideType, SlideInputType
|
||||
from src.utils import str_to_enum
|
||||
from src.util.utils import str_to_enum
|
||||
|
||||
|
||||
class Slide:
|
||||
|
||||
@ -5,7 +5,7 @@ from typing import Optional, Union, Dict, List
|
||||
from src.model.enum.VariableType import VariableType
|
||||
from src.model.enum.VariableUnit import VariableUnit
|
||||
from src.model.entity.Selectable import Selectable
|
||||
from src.utils import str_to_enum
|
||||
from src.util.utils import str_to_enum
|
||||
|
||||
|
||||
class Variable:
|
||||
|
||||
@ -3,7 +3,8 @@ import platform
|
||||
import psutil
|
||||
import socket
|
||||
|
||||
from src.utils import convert_size, get_working_directory
|
||||
from src.util.utils import get_working_directory
|
||||
from src.util.UtilFile import convert_size
|
||||
|
||||
|
||||
def get_rpi_model():
|
||||
@ -94,6 +95,14 @@ def get_default_log_file():
|
||||
return None
|
||||
|
||||
|
||||
def get_network_ipaddr():
|
||||
network_info = get_network_info()
|
||||
|
||||
if isinstance(network_info, dict):
|
||||
return network_info['ip_address']
|
||||
return None
|
||||
|
||||
|
||||
def get_all_sysinfo():
|
||||
rpi_model = get_rpi_model()
|
||||
infos = {
|
||||
|
||||
@ -9,7 +9,7 @@ from src.model.hook.HookRegistration import HookRegistration
|
||||
from src.model.hook.StaticHookRegistration import StaticHookRegistration
|
||||
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
|
||||
from src.constant.WebDirConstant import WebDirConstant
|
||||
from src.utils import get_safe_cron_descriptor, is_validate_cron_date_time, seconds_to_hhmmss, am_i_in_docker
|
||||
from src.util.utils import get_safe_cron_descriptor, is_validate_cron_date_time, seconds_to_hhmmss, am_i_in_docker
|
||||
|
||||
|
||||
class TemplateRenderer:
|
||||
|
||||
19
src/util/UtilFile.py
Normal file
19
src/util/UtilFile.py
Normal file
@ -0,0 +1,19 @@
|
||||
import os
|
||||
import uuid
|
||||
import math
|
||||
|
||||
|
||||
def randomize_filename(old_filename: str) -> str:
|
||||
new_uuid = str(uuid.uuid4())
|
||||
_, extension = os.path.splitext(old_filename)
|
||||
return f"{new_uuid}{extension}"
|
||||
|
||||
|
||||
def convert_size(size_bytes):
|
||||
if size_bytes == 0:
|
||||
return "0B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return f"{s} {size_name[i]}"
|
||||
36
src/util/UtilNetwork.py
Normal file
36
src/util/UtilNetwork.py
Normal file
@ -0,0 +1,36 @@
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from src.service.Sysinfo import get_network_ipaddr
|
||||
|
||||
|
||||
def get_ip_address() -> Optional[str]:
|
||||
try:
|
||||
os_name = platform.system().lower()
|
||||
if os_name == "linux":
|
||||
result = subprocess.run(["ip", "-4", "route", "get", "8.8.8.8"], capture_output=True, text=True)
|
||||
ip_address = result.stdout.split()[6]
|
||||
elif os_name == "darwin":
|
||||
result = subprocess.run(
|
||||
["ipconfig", "getifaddr", "en0"], capture_output=True, text=True)
|
||||
ip_address = result.stdout.strip()
|
||||
elif os_name == "windows":
|
||||
result = subprocess.run(["ipconfig"], capture_output=True, text=True)
|
||||
lines = result.stdout.split('\n')
|
||||
ip_address = None
|
||||
for line in lines:
|
||||
if "ipv4 address" in line.lower():
|
||||
ip_address = line.split(': ')[1].strip()
|
||||
break
|
||||
else:
|
||||
logging.warn(f"Unsupported OS: {os_name}")
|
||||
return get_network_ipaddr()
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
logging.error(f"Error obtaining IP address: {e}")
|
||||
return get_network_ipaddr()
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
import inspect
|
||||
import logging
|
||||
import subprocess
|
||||
import unicodedata
|
||||
import platform
|
||||
import math
|
||||
|
||||
|
||||
from typing import Optional, List, Dict
|
||||
@ -139,34 +135,6 @@ def str_to_enum(str_val: str, enum_class) -> Enum:
|
||||
return enum_item
|
||||
raise ValueError(f"{str_val} is not a valid {enum_class.__name__} item")
|
||||
|
||||
|
||||
def get_ip_address() -> Optional[str]:
|
||||
try:
|
||||
os_name = platform.system().lower()
|
||||
if os_name == "linux":
|
||||
result = subprocess.run(["ip", "-4", "route", "get", "8.8.8.8"], capture_output=True, text=True)
|
||||
ip_address = result.stdout.split()[6]
|
||||
elif os_name == "darwin":
|
||||
result = subprocess.run(
|
||||
["ipconfig", "getifaddr", "en0"], capture_output=True, text=True)
|
||||
ip_address = result.stdout.strip()
|
||||
elif os_name == "windows":
|
||||
result = subprocess.run(["ipconfig"], capture_output=True, text=True)
|
||||
lines = result.stdout.split('\n')
|
||||
ip_address = None
|
||||
for line in lines:
|
||||
if "ipv4 address" in line.lower():
|
||||
ip_address = line.split(': ')[1].strip()
|
||||
break
|
||||
else:
|
||||
logging.warn(f"Unsupported OS: {os_name}")
|
||||
return None
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
logging.error(f"Error obtaining IP address: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def regex_search(pattern: str, string: str, group: int):
|
||||
"""Shortcut method to search a string for a given pattern.
|
||||
:param str pattern:
|
||||
@ -221,22 +189,6 @@ def seconds_to_hhmmss(seconds):
|
||||
return f"{hours:02}:{minutes:02}:{secs:02}"
|
||||
|
||||
|
||||
def randomize_filename(old_filename: str) -> str:
|
||||
new_uuid = str(uuid.uuid4())
|
||||
_, extension = os.path.splitext(old_filename)
|
||||
return f"{new_uuid}{extension}"
|
||||
|
||||
|
||||
def convert_size(size_bytes):
|
||||
if size_bytes == 0:
|
||||
return "0B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return f"{s} {size_name[i]}"
|
||||
|
||||
|
||||
def get_working_directory():
|
||||
return os.getcwd()
|
||||
|
||||
@ -1,75 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
function updateTime() {
|
||||
var date = new Date();
|
||||
var hours = (date.getHours() < 10 ? '0' : '') + date.getHours();
|
||||
var minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
||||
var seconds = (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
|
||||
var dayInMonth = date.getDate();
|
||||
var month = date.getMonth();
|
||||
var year = date.getFullYear();
|
||||
var day = date.getDay();
|
||||
var dayLabels = ["{{l.basic_day_7}}", "{{l.basic_day_1}}", "{{l.basic_day_2}}", "{{l.basic_day_3}}", "{{l.basic_day_4}}", "{{l.basic_day_5}}", "{{l.basic_day_6}}"];
|
||||
var monthLabels = ["{{l.basic_month_1}}", "{{l.basic_month_2}}", "{{l.basic_month_3}}", "{{l.basic_month_4}}", "{{l.basic_month_5}}", "{{l.basic_month_6}}", "{{l.basic_month_7}}", "{{l.basic_month_8}}", "{{l.basic_month_9}}", "{{l.basic_month_10}}", "{{l.basic_month_11}}", "{{l.basic_month_12}}"];
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
const time_with_seconds = {{ 'true' if time_with_seconds.as_bool() else 'false' }};
|
||||
|
||||
var timeLabel = hours + ":" + minutes;
|
||||
var dateLabel = dayLabels[day] + " " + dayInMonth + " " + monthLabels[month] + " " + year;
|
||||
function updateTime() {
|
||||
const date = new Date();
|
||||
const hours = (date.getHours() < 10 ? '0' : '') + date.getHours();
|
||||
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
||||
const seconds = (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
|
||||
const dayInMonth = date.getDate();
|
||||
const month = date.getMonth();
|
||||
const year = date.getFullYear();
|
||||
const day = date.getDay();
|
||||
const dayLabels = ["{{l.basic_day_7}}", "{{l.basic_day_1}}", "{{l.basic_day_2}}", "{{l.basic_day_3}}", "{{l.basic_day_4}}", "{{l.basic_day_5}}", "{{l.basic_day_6}}"];
|
||||
const monthLabels = ["{{l.basic_month_1}}", "{{l.basic_month_2}}", "{{l.basic_month_3}}", "{{l.basic_month_4}}", "{{l.basic_month_5}}", "{{l.basic_month_6}}", "{{l.basic_month_7}}", "{{l.basic_month_8}}", "{{l.basic_month_9}}", "{{l.basic_month_10}}", "{{l.basic_month_11}}", "{{l.basic_month_12}}"];
|
||||
|
||||
document.getElementById('time').innerHTML = timeLabel;
|
||||
document.getElementById('date').innerHTML = dateLabel;
|
||||
setTimeout(updateTime, 1000);
|
||||
}
|
||||
const timeLabel = hours + ":" + minutes + (time_with_seconds ? ':' + seconds : '');
|
||||
const dateLabel = dayLabels[day] + " " + dayInMonth + " " + monthLabels[month] + " " + year;
|
||||
|
||||
window.addEventListener("load", updateTime);
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
font-family: 'Arial', 'sans-serif';
|
||||
color: white;
|
||||
background-color: black
|
||||
}
|
||||
#bottom {
|
||||
background: #111;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 50px 0;
|
||||
}
|
||||
#time {
|
||||
font-size: 10em;
|
||||
}
|
||||
#date {
|
||||
font-size: 3em;
|
||||
}
|
||||
#ipaddr {
|
||||
font-size: 1.25em;
|
||||
color: #888888;
|
||||
}
|
||||
#ipaddr a {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="time"></div>
|
||||
<div id="date"></div>
|
||||
<div id="bottom">
|
||||
<div id="ipaddr">
|
||||
{% set link = 'http://' ~ ipaddr ~ ':' ~ PORT ~ url_for('manage') %}
|
||||
{{
|
||||
(l.player_default_welcome_message|safe)|replace(
|
||||
'%link%',
|
||||
('<a href="' ~ url_for('manage') ~ '" target="_blank">' ~ link ~ '</a>')|safe
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
document.getElementById('time').innerHTML = timeLabel;
|
||||
document.getElementById('date').innerHTML = dateLabel;
|
||||
setTimeout(updateTime, 1000);
|
||||
}
|
||||
|
||||
window.addEventListener("load", updateTime);
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
setInterval(function(){
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "{{ url_for('sysinfo_get_ipaddr') }}", true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
setIp(xhr.responseText);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}, 5000);
|
||||
</script>
|
||||
<style>
|
||||
body { text-align: center; font-family: 'Arial', 'sans-serif'; color: white; background-color: black; }
|
||||
#bottom { display: flex; justify-content: center; align-items: center; background: #111; position: fixed; left: 0; right: 0; bottom: 0; padding: 50px 0; min-height: 50px; }
|
||||
#time { font-size: 10em; }
|
||||
#date { font-size: 3em; }
|
||||
#ipaddr { font-size: 1.25em; color: #888888; }
|
||||
#ipaddr a { color: #FFFFFF; text-decoration: none; font-weight: bold; }
|
||||
#hidden-container { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="hidden-container"></div>
|
||||
<div id="time"></div>
|
||||
<div id="date"></div>
|
||||
<div id="bottom">
|
||||
<div id="ipaddr"> </div>
|
||||
</div>
|
||||
<script>
|
||||
const js_l_common_unknown_ipaddr = '{{ l.common_unknown_ipaddr }}';
|
||||
const js_l_player_default_welcome_message = '{{ l.player_default_welcome_message }}';
|
||||
const manage_url_template = '{{ 'http://%ipaddr%:' ~ PORT ~ url_for('manage') }}';
|
||||
|
||||
const setIp = function(ipaddr) {
|
||||
const $container = document.getElementById('ipaddr');
|
||||
|
||||
if (ipaddr) {
|
||||
const link = manage_url_template.replace('%ipaddr%', ipaddr);
|
||||
$container.innerHTML = js_l_player_default_welcome_message.replace('%link%', '<a href="'+link+'" target="_blank">'+link+'</a>')
|
||||
} else {
|
||||
$container.innerHTML = js_l_common_unknown_ipaddr;
|
||||
}
|
||||
};
|
||||
setIp('{{ ipaddr if ipaddr else '' }}')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user