mirror of
https://github.com/craigerl/aprsd.git
synced 2024-11-21 23:55:17 -05:00
Add support for mobile browsers for webchat
This patch adds initial support for changing the UI for webchat based if the browser is on a mobile device.
This commit is contained in:
parent
44696fbc56
commit
f9e7195e25
@ -11,6 +11,7 @@ import time
|
||||
import aprslib
|
||||
from aprslib import util as aprslib_util
|
||||
import click
|
||||
from device_detector import DeviceDetector
|
||||
import flask
|
||||
from flask import request
|
||||
from flask.logging import default_handler
|
||||
@ -258,9 +259,12 @@ class WebChatTXThread(aprsd_thread.APRSDThread):
|
||||
msg_id = packet.get("msgNo", "0")
|
||||
msg_response = packet.get("response", None)
|
||||
|
||||
if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack":
|
||||
if (
|
||||
tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
||||
and msg_response == "ack"
|
||||
):
|
||||
self.process_ack_packet(packet)
|
||||
elif tocall == self.config["aprsd"]["callsign"]:
|
||||
elif tocall.lower() == self.config["aprsd"]["callsign"].lower():
|
||||
messaging.log_message(
|
||||
"Received Message",
|
||||
packet["raw"],
|
||||
@ -312,10 +316,7 @@ class WebChatFlask(flask_classful.FlaskView):
|
||||
|
||||
users = self.users
|
||||
|
||||
@auth.login_required
|
||||
def index(self):
|
||||
stats = self._stats()
|
||||
|
||||
def _get_transport(self, stats):
|
||||
if self.config["aprs"].get("enabled", True):
|
||||
transport = "aprs-is"
|
||||
aprs_connection = (
|
||||
@ -341,12 +342,35 @@ class WebChatFlask(flask_classful.FlaskView):
|
||||
)
|
||||
)
|
||||
|
||||
return transport, aprs_connection
|
||||
|
||||
@auth.login_required
|
||||
def index(self):
|
||||
user_agent = request.headers.get("User-Agent")
|
||||
device = DeviceDetector(user_agent).parse()
|
||||
LOG.debug(f"Device type {device.device_type()}")
|
||||
LOG.debug(f"Is mobile? {device.is_mobile()}")
|
||||
stats = self._stats()
|
||||
|
||||
if device.is_mobile():
|
||||
html_template = "mobile.html"
|
||||
else:
|
||||
html_template = "index.html"
|
||||
|
||||
# For development
|
||||
# html_template = "mobile.html"
|
||||
|
||||
LOG.debug(f"Template {html_template}")
|
||||
|
||||
transport, aprs_connection = self._get_transport(stats)
|
||||
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
|
||||
|
||||
stats["transport"] = transport
|
||||
stats["aprs_connection"] = aprs_connection
|
||||
LOG.debug(f"initial stats = {stats}")
|
||||
|
||||
return flask.render_template(
|
||||
"index.html",
|
||||
html_template,
|
||||
initial_stats=stats,
|
||||
aprs_connection=aprs_connection,
|
||||
callsign=self.config["aprsd"]["callsign"],
|
||||
|
@ -88,7 +88,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
||||
return
|
||||
|
||||
def loop(self):
|
||||
"""Process a packet recieved from aprs-is server."""
|
||||
"""Process a packet received from aprs-is server."""
|
||||
packet = self.packet
|
||||
packets.PacketList().add(packet)
|
||||
|
||||
@ -101,7 +101,10 @@ class APRSDProcessPacketThread(APRSDThread):
|
||||
|
||||
# We don't put ack packets destined for us through the
|
||||
# plugins.
|
||||
if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack":
|
||||
if (
|
||||
tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
||||
and msg_response == "ack"
|
||||
):
|
||||
self.process_ack_packet(packet)
|
||||
else:
|
||||
# It's not an ACK for us, so lets run it through
|
||||
|
221
aprsd/web/chat/static/js/send-message-mobile.js
Normal file
221
aprsd/web/chat/static/js/send-message-mobile.js
Normal file
@ -0,0 +1,221 @@
|
||||
var cleared = false;
|
||||
var callsign_list = {};
|
||||
var message_list = {};
|
||||
|
||||
function size_dict(d){c=0; for (i in d) ++c; return c}
|
||||
|
||||
function init_chat() {
|
||||
const socket = io("/sendmsg");
|
||||
socket.on('connect', function () {
|
||||
console.log("Connected to socketio");
|
||||
});
|
||||
socket.on('connected', function(msg) {
|
||||
console.log("Connected!");
|
||||
console.log(msg);
|
||||
});
|
||||
|
||||
socket.on("sent", function(msg) {
|
||||
if (cleared == false) {
|
||||
var msgsdiv = $("#msgsTabsDiv");
|
||||
msgsdiv.html('')
|
||||
cleared = true
|
||||
}
|
||||
sent_msg(msg);
|
||||
});
|
||||
|
||||
socket.on("ack", function(msg) {
|
||||
update_msg(msg);
|
||||
});
|
||||
|
||||
socket.on("new", function(msg) {
|
||||
if (cleared == false) {
|
||||
var msgsdiv = $("#msgsTabsDiv");
|
||||
msgsdiv.html('')
|
||||
cleared = true
|
||||
}
|
||||
from_msg(msg);
|
||||
});
|
||||
|
||||
$("#sendform").submit(function(event) {
|
||||
event.preventDefault();
|
||||
msg = {'to': $('#to_call').val(),
|
||||
'message': $('#message').val(),
|
||||
}
|
||||
socket.emit("send", msg);
|
||||
$('#message').val('');
|
||||
$('#to_call').val('');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function add_callsign(callsign) {
|
||||
/* Ensure a callsign exists in the left hand nav */
|
||||
dropdown = $('#callsign_dropdown')
|
||||
|
||||
if (callsign in callsign_list) {
|
||||
console.log(callsign+' already in list.')
|
||||
return false
|
||||
}
|
||||
|
||||
var callsignTabs = $("#callsignTabs");
|
||||
tab_name = tab_string(callsign);
|
||||
tab_content = tab_content_name(callsign);
|
||||
divname = content_divname(callsign);
|
||||
|
||||
item_html = '<div class="active item" id="'+tab_name+'" onclick="openCallsign(event, \''+callsign+'\');">'+callsign+'</div>';
|
||||
callsignTabs.append(item_html);
|
||||
|
||||
callsign_list[callsign] = {'name': callsign, 'value': callsign, 'text': callsign}
|
||||
return true
|
||||
}
|
||||
|
||||
function append_message(callsign, msg, msg_html) {
|
||||
console.log('append_message');
|
||||
new_callsign = false
|
||||
if (!message_list.hasOwnProperty(callsign)) {
|
||||
message_list[callsign] = new Array();
|
||||
}
|
||||
message_list[callsign].push(msg);
|
||||
if (new_callsign) {
|
||||
//click on the new tab
|
||||
click_div = '#'+tab_string(callsign);
|
||||
console.log("Click on "+click_div);
|
||||
$(click_div).click();
|
||||
}
|
||||
|
||||
// Find the right div to place the html
|
||||
new_callsign = add_callsign(callsign);
|
||||
append_message_html(callsign, msg_html, new_callsign);
|
||||
}
|
||||
|
||||
function tab_string(callsign) {
|
||||
return "msgs"+callsign;
|
||||
}
|
||||
|
||||
function tab_content_name(callsign) {
|
||||
return tab_string(callsign)+"Content";
|
||||
}
|
||||
|
||||
function content_divname(callsign) {
|
||||
return "#"+tab_content_name(callsign);
|
||||
}
|
||||
|
||||
function append_message_html(callsign, msg_html, new_callsign) {
|
||||
var msgsTabs = $('#msgsTabsDiv');
|
||||
divname_str = tab_content_name(callsign);
|
||||
divname = content_divname(callsign);
|
||||
if (new_callsign) {
|
||||
// we have to add a new DIV
|
||||
msg_div_html = '<div class="tabcontent" id="'+divname_str+'" style="height:450px;">'+msg_html+'</div>';
|
||||
msgsTabs.append(msg_div_html);
|
||||
} else {
|
||||
var msgDiv = $(divname);
|
||||
msgDiv.append(msg_html);
|
||||
}
|
||||
|
||||
$(divname).animate({scrollTop: $(divname)[0].scrollHeight}, "slow");
|
||||
}
|
||||
|
||||
function create_message_html(time, from, to, message, ack) {
|
||||
msg_html = '<div class="item">';
|
||||
msg_html += '<div class="tiny text">'+time+'</div>';
|
||||
msg_html += '<div class="middle aligned content">';
|
||||
msg_html += '<div class="tiny red header">'+from+'</div>';
|
||||
if (ack) {
|
||||
msg_html += '<i class="thumbs down outline icon" id="' + ack_id + '" data-content="Waiting for ACK"></i>';
|
||||
} else {
|
||||
msg_html += '<i class="phone volume icon" data-content="Recieved Message"></i>';
|
||||
}
|
||||
msg_html += '<div class="middle aligned content">> </div>';
|
||||
msg_html += '</div>';
|
||||
msg_html += '<div class="middle aligned content">'+message+'</div>';
|
||||
msg_html += '</div><br>';
|
||||
|
||||
return msg_html
|
||||
}
|
||||
|
||||
function sent_msg(msg) {
|
||||
var msgsdiv = $("#sendMsgsDiv");
|
||||
|
||||
ts_str = msg["ts"].toString();
|
||||
ts = ts_str.split(".")[0]*1000;
|
||||
id = ts_str.split('.')[0]
|
||||
ack_id = "ack_" + id
|
||||
|
||||
var d = new Date(ts).toLocaleDateString("en-US")
|
||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
||||
|
||||
msg_html = create_message_html(t, msg['from'], msg['to'], msg['message'], ack_id);
|
||||
append_message(msg['to'], msg, msg_html);
|
||||
}
|
||||
|
||||
function from_msg(msg) {
|
||||
var msgsdiv = $("#sendMsgsDiv");
|
||||
|
||||
// We have an existing entry
|
||||
ts_str = msg["ts"].toString();
|
||||
ts = ts_str.split(".")[0]*1000;
|
||||
id = ts_str.split('.')[0]
|
||||
ack_id = "ack_" + id
|
||||
|
||||
var d = new Date(ts).toLocaleDateString("en-US")
|
||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
||||
|
||||
from = msg['from']
|
||||
msg_html = create_message_html(t, from, false, msg['message'], false);
|
||||
append_message(from, msg, msg_html);
|
||||
}
|
||||
|
||||
function update_msg(msg) {
|
||||
var msgsdiv = $("#sendMsgsDiv");
|
||||
// We have an existing entry
|
||||
ts_str = msg["ts"].toString();
|
||||
id = ts_str.split('.')[0]
|
||||
pretty_id = "pretty_" + id
|
||||
loader_id = "loader_" + id
|
||||
ack_id = "ack_" + id
|
||||
span_id = "span_" + id
|
||||
|
||||
|
||||
|
||||
if (msg['ack'] == true) {
|
||||
var loader_div = $('#' + loader_id);
|
||||
var ack_div = $('#' + ack_id);
|
||||
loader_div.removeClass('ui active inline loader');
|
||||
loader_div.addClass('ui disabled loader');
|
||||
ack_div.removeClass('thumbs up outline icon');
|
||||
ack_div.addClass('thumbs up outline icon');
|
||||
}
|
||||
|
||||
$('.ui.accordion').accordion('refresh');
|
||||
}
|
||||
|
||||
function callsign_select(callsign) {
|
||||
var tocall = $("#to_call");
|
||||
tocall.val(callsign);
|
||||
}
|
||||
|
||||
function reset_Tabs() {
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function openCallsign(evt, callsign) {
|
||||
var i, tabcontent, tablinks;
|
||||
|
||||
tab_content = tab_content_name(callsign);
|
||||
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
document.getElementById(tab_content).style.display = "block";
|
||||
evt.target.className += " active";
|
||||
callsign_select(callsign);
|
||||
}
|
@ -66,16 +66,14 @@
|
||||
<button type="button" class="ui button" id="send_beacon" value="Send GPS Beacon">Send GPS Beacon</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui grid">
|
||||
<div class="three wide column">
|
||||
<div class="tab" id="callsignTabs">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ten wide column ui raised segment" id="msgsTabsDiv" style="height:450px;padding:0px;">
|
||||
|
||||
</div>
|
||||
<div class="ui grid">
|
||||
<div class="three wide column">
|
||||
<div class="tab" id="callsignTabs"></div>
|
||||
</div>
|
||||
<div class="ten wide column ui raised segment" id="msgsTabsDiv" style="height:450px;padding:0px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
80
aprsd/web/chat/templates/mobile.html
Normal file
80
aprsd/web/chat/templates/mobile.html
Normal file
@ -0,0 +1,80 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
||||
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/index.css">
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script src="/static/js/send-message-mobile.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var initial_stats = {{ initial_stats|tojson|safe }};
|
||||
|
||||
var memory_chart = null
|
||||
var message_chart = null
|
||||
|
||||
$(document).ready(function() {
|
||||
console.log(initial_stats);
|
||||
start_update();
|
||||
init_chat();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='ui text container'>
|
||||
<h1 class='ui dividing header'>APRSD WebChat {{ version }}</h1>
|
||||
</div>
|
||||
|
||||
<div class='ui grid text container' style="padding-bottom: 5px;">
|
||||
<div class='left floated twelve wide column'>
|
||||
<span style='color: green'>{{ callsign }}</span>
|
||||
connected to
|
||||
<span style='color: blue' id='aprs_connection'>{{ aprs_connection|safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class='right floated four wide column'>
|
||||
<span id='uptime'>NONE</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sendMsgDiv" class="ui grid" align="left" style="padding-top: 2px;">
|
||||
<h3 class="sixteen wide column ui dividing header">Send Message</h3>
|
||||
<form id="sendform" name="sendmsg" action="">
|
||||
<div class="sixteen wide column ui left labeled icon input">
|
||||
<div class="ui label">Callsign</div>
|
||||
<input type="text" name="to_call" id="to_call" placeholder="To Callsign" size="11" maxlength="9">
|
||||
<i class="users icon"></i>
|
||||
</div>
|
||||
<div class="sixteen wide column ui left labeled icon input" style="padding-bottom: 5px;">
|
||||
<label for="message" class="ui label">Message</label>
|
||||
<input type="text" name="message" id="message" maxlength="40" placeholder="Message">
|
||||
<i class="comment icon"></i>
|
||||
</div>
|
||||
<div class="right floated column">
|
||||
<input type="submit" name="submit" class="ui button" id="send_msg" value="Send" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="ui grid">
|
||||
<div class="ui top attached tabular raised menu" id="callsignTabs">
|
||||
</div>
|
||||
<div class="sixteen wide column ui bottom attached raised tab segment" id="msgsTabsDiv" style="height:250px;padding:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui text container" style="padding-top: 40px">
|
||||
<a href="https://badge.fury.io/py/aprsd"><img src="https://badge.fury.io/py/aprsd.svg" alt="PyPI version" height="18"></a>
|
||||
<a href="https://github.com/craigerl/aprsd"><img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" height="18"></a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -7,13 +7,12 @@
|
||||
add-trailing-comma==2.3.0 # via gray
|
||||
alabaster==0.7.12 # via sphinx
|
||||
attrs==22.1.0 # via jsonschema, pytest
|
||||
autoflake==1.7.7 # via gray
|
||||
autoflake==2.0.0 # via gray
|
||||
babel==2.11.0 # via sphinx
|
||||
black==22.10.0 # via gray
|
||||
bleach==5.0.1 # via readme-renderer
|
||||
build==0.9.0 # via pip-tools
|
||||
certifi==2022.9.24 # via requests
|
||||
cffi==1.15.1 # via cryptography
|
||||
cfgv==3.3.1 # via pre-commit
|
||||
charset-normalizer==2.1.1 # via requests
|
||||
click==8.1.3 # via black, pip-tools
|
||||
@ -21,7 +20,6 @@ colorlog==6.7.0 # via prettylog
|
||||
commonmark==0.9.1 # via rich
|
||||
configargparse==1.5.3 # via gray
|
||||
coverage[toml]==6.5.0 # via pytest-cov
|
||||
cryptography==38.0.3 # via secretstorage
|
||||
distlib==0.3.6 # via virtualenv
|
||||
docutils==0.19 # via readme-renderer, sphinx
|
||||
exceptiongroup==1.0.4 # via pytest
|
||||
@ -38,9 +36,8 @@ importlib-resources==5.10.0 # via fixit, jsonschema
|
||||
iniconfig==1.1.1 # via pytest
|
||||
isort==5.10.1 # via -r dev-requirements.in, gray
|
||||
jaraco-classes==3.2.3 # via keyring
|
||||
jeepney==0.8.0 # via keyring, secretstorage
|
||||
jinja2==3.1.2 # via sphinx
|
||||
jsonschema==4.17.1 # via fixit
|
||||
jsonschema==4.17.3 # via fixit
|
||||
keyring==23.11.0 # via twine
|
||||
libcst==0.4.9 # via fixit
|
||||
markupsafe==2.1.1 # via jinja2
|
||||
@ -54,7 +51,7 @@ pathspec==0.10.2 # via black
|
||||
pep517==0.13.0 # via build
|
||||
pep8-naming==0.13.2 # via -r dev-requirements.in
|
||||
pip-tools==6.10.0 # via -r dev-requirements.in
|
||||
pkginfo==1.8.3 # via twine
|
||||
pkginfo==1.9.2 # via twine
|
||||
pkgutil-resolve-name==1.3.10 # via jsonschema
|
||||
platformdirs==2.5.4 # via black, virtualenv
|
||||
pluggy==1.0.0 # via pytest, tox
|
||||
@ -62,7 +59,6 @@ pre-commit==2.20.0 # via -r dev-requirements.in
|
||||
prettylog==0.3.0 # via gray
|
||||
py==1.11.0 # via tox
|
||||
pycodestyle==2.10.0 # via flake8
|
||||
pycparser==2.21 # via cffi
|
||||
pyflakes==3.0.1 # via autoflake, flake8
|
||||
pygments==2.13.0 # via readme-renderer, rich, sphinx
|
||||
pyparsing==3.0.9 # via packaging
|
||||
@ -70,14 +66,13 @@ pyrsistent==0.19.2 # via jsonschema
|
||||
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
|
||||
pytest-cov==4.0.0 # via -r dev-requirements.in
|
||||
pytz==2022.6 # via babel
|
||||
pyupgrade==3.2.2 # via gray
|
||||
pyupgrade==3.2.3 # via gray
|
||||
pyyaml==6.0 # via fixit, libcst, pre-commit
|
||||
readme-renderer==37.3 # via twine
|
||||
requests==2.28.1 # via requests-toolbelt, sphinx, twine
|
||||
requests-toolbelt==0.10.1 # via twine
|
||||
rfc3986==2.0.0 # via twine
|
||||
rich==12.6.0 # via twine
|
||||
secretstorage==3.3.3 # via keyring
|
||||
six==1.16.0 # via bleach, tox
|
||||
snowballstemmer==2.2.0 # via sphinx
|
||||
sphinx==5.3.0 # via -r dev-requirements.in
|
||||
@ -98,10 +93,10 @@ ujson==5.5.0 # via fast-json
|
||||
unify==0.5 # via gray
|
||||
untokenize==0.1.1 # via unify
|
||||
urllib3==1.26.13 # via requests, twine
|
||||
virtualenv==20.16.7 # via pre-commit, tox
|
||||
virtualenv==20.17.0 # via pre-commit, tox
|
||||
webencodings==0.5.1 # via bleach
|
||||
wheel==0.38.4 # via pip-tools
|
||||
zipp==3.10.0 # via importlib-metadata, importlib-resources
|
||||
zipp==3.11.0 # via importlib-metadata, importlib-resources
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
|
@ -24,3 +24,5 @@ wrapt
|
||||
# kiss3 uses attrs
|
||||
kiss3
|
||||
attrs==22.1.0
|
||||
# for mobile checking
|
||||
device-detector
|
||||
|
@ -15,6 +15,7 @@ charset-normalizer==2.1.1 # via requests
|
||||
click==8.1.3 # via -r requirements.in, click-completion, flask
|
||||
click-completion==0.5.2 # via -r requirements.in
|
||||
commonmark==0.9.1 # via rich
|
||||
device-detector==5.0.1 # via -r requirements.in
|
||||
dnspython==2.2.1 # via eventlet
|
||||
eventlet==0.33.2 # via -r requirements.in
|
||||
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
||||
@ -37,7 +38,8 @@ pyserial-asyncio==0.6 # via kiss3
|
||||
python-engineio==4.3.4 # via python-socketio
|
||||
python-socketio==5.7.2 # via flask-socketio
|
||||
pytz==2022.6 # via -r requirements.in
|
||||
pyyaml==6.0 # via -r requirements.in
|
||||
pyyaml==6.0 # via -r requirements.in, device-detector
|
||||
regex==2022.10.31 # via device-detector
|
||||
requests==2.28.1 # via -r requirements.in, update-checker
|
||||
rich==12.6.0 # via -r requirements.in
|
||||
shellingham==1.5.0 # via click-completion
|
||||
@ -50,4 +52,4 @@ update-checker==0.18.0 # via -r requirements.in
|
||||
urllib3==1.26.13 # via requests
|
||||
werkzeug==2.1.2 # via -r requirements.in, flask
|
||||
wrapt==1.14.1 # via -r requirements.in
|
||||
zipp==3.10.0 # via importlib-metadata
|
||||
zipp==3.11.0 # via importlib-metadata
|
||||
|
Loading…
Reference in New Issue
Block a user