cwc
This commit is contained in:
parent
a77c858634
commit
19b9f7fd28
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
venv/
|
||||
config.py
|
||||
venv/
|
||||
config.py
|
||||
**/*.pyc
|
32
.vscode/launch.json
vendored
32
.vscode/launch.json
vendored
@ -1,17 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python Debugger: Current File",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
"args": ["--host=0.0.0.0"]
|
||||
}
|
||||
]
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python Debugger: Current File",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
"args": ["--host=0.0.0.0"]
|
||||
}
|
||||
]
|
||||
}
|
372
app.py
372
app.py
@ -1,186 +1,186 @@
|
||||
# app.py
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, send_file, jsonify
|
||||
from flask_caching import Cache
|
||||
import requests.auth
|
||||
import os
|
||||
from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired
|
||||
from lib.reqs import get_urls, get_user_accounts, add_user_account, delete_user_account, get_user_accounts_count
|
||||
from flask import send_from_directory
|
||||
import requests
|
||||
import base64
|
||||
from flask import Flask
|
||||
from config import DevelopmentConfig
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
os.environ["OMP_NUM_THREADS"] = "1"
|
||||
os.environ["MKL_NUM_THREADS"] = "1"
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(
|
||||
DevelopmentConfig
|
||||
)
|
||||
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache"})
|
||||
|
||||
ocr = PaddleOCR(use_angle_cls=True, lang='en') # Adjust language if needed
|
||||
|
||||
app.config['SESSION_COOKIE_SECURE'] = True # Only send cookie over HTTPS
|
||||
app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent JavaScript access
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Adjust for cross-site requests
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = 60 * 60 * 24 * 365 # 1 year in seconds
|
||||
cache.clear() # Clears all cache entries
|
||||
|
||||
@app.before_request
|
||||
def make_session_permanent():
|
||||
session.permanent = True
|
||||
|
||||
@app.route('/manifest.json')
|
||||
def serve_manifest():
|
||||
return send_file('manifest.json', mimetype='application/manifest+json')
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "static"),
|
||||
"favicon.ico",
|
||||
mimetype="image/vnd.microsoft.icon",
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
# If the user is logged in, redirect to a protected page like /accounts
|
||||
if session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/home")
|
||||
@cache.cached(timeout=60) # cache for 120 seconds
|
||||
def home():
|
||||
if session.get("logged_in"):
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
all_accounts = get_user_accounts(base_url, session["auth_credentials"])
|
||||
count = len(all_accounts)
|
||||
current_month_accounts = filter_accounts_next_30_days(all_accounts)
|
||||
expired_accounts = filter_accounts_expired(all_accounts)
|
||||
return render_template(
|
||||
"home.html",
|
||||
username=session["username"],
|
||||
accounts=count,
|
||||
current_month_accounts=current_month_accounts,
|
||||
expired_accounts=expired_accounts,
|
||||
)
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/login", methods=["POST"])
|
||||
def login():
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
# Encode the username and password in Base64
|
||||
credentials = f"{username}:{password}"
|
||||
encoded_credentials = base64.b64encode(credentials.encode()).decode()
|
||||
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
login_url = f"{base_url}/Login" # Construct the full URL
|
||||
|
||||
# Send GET request to the external login API with Basic Auth
|
||||
response = requests.get(
|
||||
login_url, auth=requests.auth.HTTPBasicAuth(username, password)
|
||||
)
|
||||
|
||||
# Check if login was successful
|
||||
if response.status_code == 200 and response.json().get("auth") == "Success":
|
||||
# Set session variable to indicate the user is logged in
|
||||
session["logged_in"] = True
|
||||
session["username"] = username
|
||||
session["auth_credentials"] = encoded_credentials
|
||||
return redirect(url_for("home")) # Redirect to the Accounts page
|
||||
else:
|
||||
# Show error on the login page
|
||||
error = "Invalid username or password. Please try again."
|
||||
return render_template("index.html", error=error)
|
||||
|
||||
|
||||
@app.route("/urls", methods=["GET"])
|
||||
@cache.cached(timeout=300) # cache for 5 minutes
|
||||
def urls():
|
||||
# Check if the user is logged in
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
# Placeholder content for Accounts page
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
return render_template(
|
||||
"urls.html", urls=get_urls(base_url, session["auth_credentials"])
|
||||
)
|
||||
|
||||
|
||||
@app.route("/accounts", methods=["GET"])
|
||||
@cache.cached(timeout=120) # cache for 120 seconds
|
||||
def user_accounts():
|
||||
# Check if the user is logged in
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
# Placeholder content for Accounts page
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
return render_template(
|
||||
"user_accounts.html",
|
||||
username=session["username"],
|
||||
user_accounts=get_user_accounts(base_url, session["auth_credentials"]),
|
||||
auth=session["auth_credentials"],
|
||||
)
|
||||
|
||||
|
||||
@app.route("/accounts/add", methods=["GET", "POST"])
|
||||
def add_account():
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
stream = request.form["stream"]
|
||||
|
||||
if add_user_account(
|
||||
base_url, session["auth_credentials"], username, password, stream
|
||||
):
|
||||
cache.clear() # Clears all cache entries
|
||||
return redirect(url_for("user_accounts"))
|
||||
return render_template("add_account.html")
|
||||
|
||||
return render_template("add_account.html")
|
||||
|
||||
|
||||
@app.route("/accounts/delete", methods=["POST"])
|
||||
def delete_account():
|
||||
stream = request.form.get("stream")
|
||||
username = request.form.get("username")
|
||||
base_url = app.config["BASE_URL"]
|
||||
|
||||
if delete_user_account(base_url, session["auth_credentials"], stream, username):
|
||||
cache.clear() # Clears all cache entries
|
||||
return redirect(url_for("user_accounts"))
|
||||
return redirect(url_for("user_accounts"))
|
||||
|
||||
|
||||
@app.route('/OCRupload', methods=['POST'])
|
||||
def OCRupload():
|
||||
if 'image' not in request.files:
|
||||
return jsonify({"error": "No image file found"}), 400
|
||||
# Get the uploaded file
|
||||
file = request.files['image']
|
||||
try:
|
||||
image = Image.open(file.stream)
|
||||
image_np = np.array(image)
|
||||
result = ocr.ocr(image_np)
|
||||
# Extract text
|
||||
extracted_text = []
|
||||
for line in result[0]:
|
||||
extracted_text.append(line[1][0])
|
||||
return render_template("add_account.html", username=extracted_text[2], password=extracted_text[3])
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=app.config["DEBUG"], host=app.config["HOST"], port=app.config["PORT"])
|
||||
# app.py
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, send_file, jsonify
|
||||
from flask_caching import Cache
|
||||
import requests.auth
|
||||
import os
|
||||
from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired
|
||||
from lib.reqs import get_urls, get_user_accounts, add_user_account, delete_user_account, get_user_accounts_count
|
||||
from flask import send_from_directory
|
||||
import requests
|
||||
import base64
|
||||
from flask import Flask
|
||||
from config import DevelopmentConfig
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
os.environ["OMP_NUM_THREADS"] = "1"
|
||||
os.environ["MKL_NUM_THREADS"] = "1"
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(
|
||||
DevelopmentConfig
|
||||
)
|
||||
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache"})
|
||||
|
||||
ocr = PaddleOCR(use_angle_cls=True, lang='en') # Adjust language if needed
|
||||
|
||||
app.config['SESSION_COOKIE_SECURE'] = True # Only send cookie over HTTPS
|
||||
app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent JavaScript access
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Adjust for cross-site requests
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = 60 * 60 * 24 * 365 # 1 year in seconds
|
||||
cache.clear() # Clears all cache entries
|
||||
|
||||
@app.before_request
|
||||
def make_session_permanent():
|
||||
session.permanent = True
|
||||
|
||||
@app.route('/manifest.json')
|
||||
def serve_manifest():
|
||||
return send_file('manifest.json', mimetype='application/manifest+json')
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "static"),
|
||||
"favicon.ico",
|
||||
mimetype="image/vnd.microsoft.icon",
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
# If the user is logged in, redirect to a protected page like /accounts
|
||||
if session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/home")
|
||||
@cache.cached(timeout=60) # cache for 120 seconds
|
||||
def home():
|
||||
if session.get("logged_in"):
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
all_accounts = get_user_accounts(base_url, session["auth_credentials"])
|
||||
count = len(all_accounts)
|
||||
current_month_accounts = filter_accounts_next_30_days(all_accounts)
|
||||
expired_accounts = filter_accounts_expired(all_accounts)
|
||||
return render_template(
|
||||
"home.html",
|
||||
username=session["username"],
|
||||
accounts=count,
|
||||
current_month_accounts=current_month_accounts,
|
||||
expired_accounts=expired_accounts,
|
||||
)
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/login", methods=["POST"])
|
||||
def login():
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
# Encode the username and password in Base64
|
||||
credentials = f"{username}:{password}"
|
||||
encoded_credentials = base64.b64encode(credentials.encode()).decode()
|
||||
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
login_url = f"{base_url}/Login" # Construct the full URL
|
||||
|
||||
# Send GET request to the external login API with Basic Auth
|
||||
response = requests.get(
|
||||
login_url, auth=requests.auth.HTTPBasicAuth(username, password)
|
||||
)
|
||||
|
||||
# Check if login was successful
|
||||
if response.status_code == 200 and response.json().get("auth") == "Success":
|
||||
# Set session variable to indicate the user is logged in
|
||||
session["logged_in"] = True
|
||||
session["username"] = username
|
||||
session["auth_credentials"] = encoded_credentials
|
||||
return redirect(url_for("home")) # Redirect to the Accounts page
|
||||
else:
|
||||
# Show error on the login page
|
||||
error = "Invalid username or password. Please try again."
|
||||
return render_template("index.html", error=error)
|
||||
|
||||
|
||||
@app.route("/urls", methods=["GET"])
|
||||
@cache.cached(timeout=300) # cache for 5 minutes
|
||||
def urls():
|
||||
# Check if the user is logged in
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
# Placeholder content for Accounts page
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
return render_template(
|
||||
"urls.html", urls=get_urls(base_url, session["auth_credentials"])
|
||||
)
|
||||
|
||||
|
||||
@app.route("/accounts", methods=["GET"])
|
||||
@cache.cached(timeout=120) # cache for 120 seconds
|
||||
def user_accounts():
|
||||
# Check if the user is logged in
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
# Placeholder content for Accounts page
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
return render_template(
|
||||
"user_accounts.html",
|
||||
username=session["username"],
|
||||
user_accounts=get_user_accounts(base_url, session["auth_credentials"]),
|
||||
auth=session["auth_credentials"],
|
||||
)
|
||||
|
||||
|
||||
@app.route("/accounts/add", methods=["GET", "POST"])
|
||||
def add_account():
|
||||
base_url = app.config["BASE_URL"] # Access base_url from the config
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
stream = request.form["stream"]
|
||||
|
||||
if add_user_account(
|
||||
base_url, session["auth_credentials"], username, password, stream
|
||||
):
|
||||
cache.clear() # Clears all cache entries
|
||||
return redirect(url_for("user_accounts"))
|
||||
return render_template("add_account.html")
|
||||
|
||||
return render_template("add_account.html")
|
||||
|
||||
|
||||
@app.route("/accounts/delete", methods=["POST"])
|
||||
def delete_account():
|
||||
stream = request.form.get("stream")
|
||||
username = request.form.get("username")
|
||||
base_url = app.config["BASE_URL"]
|
||||
|
||||
if delete_user_account(base_url, session["auth_credentials"], stream, username):
|
||||
cache.clear() # Clears all cache entries
|
||||
return redirect(url_for("user_accounts"))
|
||||
return redirect(url_for("user_accounts"))
|
||||
|
||||
|
||||
@app.route('/OCRupload', methods=['POST'])
|
||||
def OCRupload():
|
||||
if 'image' not in request.files:
|
||||
return jsonify({"error": "No image file found"}), 400
|
||||
# Get the uploaded file
|
||||
file = request.files['image']
|
||||
try:
|
||||
image = Image.open(file.stream)
|
||||
image_np = np.array(image)
|
||||
result = ocr.ocr(image_np)
|
||||
# Extract text
|
||||
extracted_text = []
|
||||
for line in result[0]:
|
||||
extracted_text.append(line[1][0])
|
||||
return render_template("add_account.html", username=extracted_text[2], password=extracted_text[3])
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=app.config["DEBUG"], host=app.config["HOST"], port=app.config["PORT"])
|
||||
|
@ -1,19 +1,19 @@
|
||||
from flask import Flask, jsonify
|
||||
from config import DevelopmentConfig
|
||||
from lib.mysql import execute_query
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(DevelopmentConfig)
|
||||
|
||||
@app.route('/getUserAccounts', methods=['GET'])
|
||||
def get_user_accounts():
|
||||
# Use the execute_query function to get user accounts
|
||||
|
||||
data = execute_query("SELECT COUNT(*) AS account_count FROM userAccounts WHERE userID = %s;", (1,))
|
||||
if data is None:
|
||||
return jsonify({"error": "Database query failed"}), 500
|
||||
return jsonify(data), 200
|
||||
|
||||
# Run the app
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=app.config["DEBUG"], port=app.config["PORT"])
|
||||
from flask import Flask, jsonify
|
||||
from config import DevelopmentConfig
|
||||
from lib.mysql import execute_query
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(DevelopmentConfig)
|
||||
|
||||
@app.route('/getUserAccounts', methods=['GET'])
|
||||
def get_user_accounts():
|
||||
# Use the execute_query function to get user accounts
|
||||
|
||||
data = execute_query("SELECT COUNT(*) AS account_count FROM userAccounts WHERE userID = %s;", (1,))
|
||||
if data is None:
|
||||
return jsonify({"error": "Database query failed"}), 500
|
||||
return jsonify(data), 200
|
||||
|
||||
# Run the app
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=app.config["DEBUG"], port=app.config["PORT"])
|
||||
|
@ -1,37 +1,37 @@
|
||||
import mysql.connector
|
||||
from flask import current_app
|
||||
|
||||
def execute_query(query, params=None, fetch_one=False):
|
||||
"""Execute a SQL query and optionally fetch results."""
|
||||
try:
|
||||
# Get database configuration from the current app context
|
||||
db_config = {
|
||||
"host": current_app.config['DBHOST'],
|
||||
"user": current_app.config['DBUSER'],
|
||||
"password": current_app.config['DBPASS'],
|
||||
"database": current_app.config['DATABASE'],
|
||||
}
|
||||
|
||||
# Establish database connection
|
||||
connection = mysql.connector.connect(**db_config)
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Execute the query with optional parameters
|
||||
cursor.execute(query, params)
|
||||
|
||||
# Fetch results if it's a SELECT query
|
||||
if query.strip().upper().startswith("SELECT"):
|
||||
result = cursor.fetchone() if fetch_one else cursor.fetchall()
|
||||
else:
|
||||
# Commit changes for INSERT, UPDATE, DELETE
|
||||
connection.commit()
|
||||
result = cursor.rowcount # Number of affected rows
|
||||
|
||||
# Close the database connection
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return result
|
||||
except mysql.connector.Error as err:
|
||||
print("Error: ", err)
|
||||
return None
|
||||
import mysql.connector
|
||||
from flask import current_app
|
||||
|
||||
def execute_query(query, params=None, fetch_one=False):
|
||||
"""Execute a SQL query and optionally fetch results."""
|
||||
try:
|
||||
# Get database configuration from the current app context
|
||||
db_config = {
|
||||
"host": current_app.config['DBHOST'],
|
||||
"user": current_app.config['DBUSER'],
|
||||
"password": current_app.config['DBPASS'],
|
||||
"database": current_app.config['DATABASE'],
|
||||
}
|
||||
|
||||
# Establish database connection
|
||||
connection = mysql.connector.connect(**db_config)
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Execute the query with optional parameters
|
||||
cursor.execute(query, params)
|
||||
|
||||
# Fetch results if it's a SELECT query
|
||||
if query.strip().upper().startswith("SELECT"):
|
||||
result = cursor.fetchone() if fetch_one else cursor.fetchall()
|
||||
else:
|
||||
# Commit changes for INSERT, UPDATE, DELETE
|
||||
connection.commit()
|
||||
result = cursor.rowcount # Number of affected rows
|
||||
|
||||
# Close the database connection
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return result
|
||||
except mysql.connector.Error as err:
|
||||
print("Error: ", err)
|
||||
return None
|
||||
|
@ -1,11 +1,11 @@
|
||||
# config.py
|
||||
|
||||
class Config:
|
||||
DEBUG = False
|
||||
BASE_URL = '' # Set your base URL here
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
|
||||
class ProductionConfig(Config):
|
||||
BASE_URL = '' # Production base URL
|
||||
# config.py
|
||||
|
||||
class Config:
|
||||
DEBUG = False
|
||||
BASE_URL = '' # Set your base URL here
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
|
||||
class ProductionConfig(Config):
|
||||
BASE_URL = '' # Production base URL
|
||||
|
@ -1,46 +1,46 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict
|
||||
|
||||
def filter_accounts_next_30_days(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]:
|
||||
"""Filter accounts whose expiry date falls within the next 30 days.
|
||||
|
||||
Args:
|
||||
accounts (List[Dict[str, int]]): A list of account dictionaries, each containing
|
||||
an 'expiaryDate' key with an epoch timestamp as its value.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, int]]: A list of accounts expiring within the next 30 days.
|
||||
"""
|
||||
now = datetime.now()
|
||||
thirty_days_later = now + timedelta(days=30)
|
||||
|
||||
# Convert current time and 30 days later to epoch timestamps
|
||||
now_timestamp = int(now.timestamp())
|
||||
thirty_days_later_timestamp = int(thirty_days_later.timestamp())
|
||||
|
||||
# Filter accounts with expiryDate within the next 30 days
|
||||
return [
|
||||
account for account in accounts
|
||||
if now_timestamp <= account['expiaryDate'] < thirty_days_later_timestamp
|
||||
]
|
||||
|
||||
def filter_accounts_expired(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]:
|
||||
"""Filter accounts whose expiry date has passed.
|
||||
|
||||
Args:
|
||||
accounts (List[Dict[str, int]]): A list of account dictionaries, each containing
|
||||
an 'expiaryDate' key with an epoch timestamp as its value.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, int]]: A list of accounts that have expired.
|
||||
"""
|
||||
# Get the current epoch timestamp
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
|
||||
# Filter accounts where the current date is greater than the expiryDate
|
||||
expired_accounts = [
|
||||
account for account in accounts
|
||||
if account['expiaryDate'] < current_timestamp
|
||||
]
|
||||
|
||||
return expired_accounts
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict
|
||||
|
||||
def filter_accounts_next_30_days(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]:
|
||||
"""Filter accounts whose expiry date falls within the next 30 days.
|
||||
|
||||
Args:
|
||||
accounts (List[Dict[str, int]]): A list of account dictionaries, each containing
|
||||
an 'expiaryDate' key with an epoch timestamp as its value.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, int]]: A list of accounts expiring within the next 30 days.
|
||||
"""
|
||||
now = datetime.now()
|
||||
thirty_days_later = now + timedelta(days=30)
|
||||
|
||||
# Convert current time and 30 days later to epoch timestamps
|
||||
now_timestamp = int(now.timestamp())
|
||||
thirty_days_later_timestamp = int(thirty_days_later.timestamp())
|
||||
|
||||
# Filter accounts with expiryDate within the next 30 days
|
||||
return [
|
||||
account for account in accounts
|
||||
if now_timestamp <= account['expiaryDate'] < thirty_days_later_timestamp
|
||||
]
|
||||
|
||||
def filter_accounts_expired(accounts: List[Dict[str, int]]) -> List[Dict[str, int]]:
|
||||
"""Filter accounts whose expiry date has passed.
|
||||
|
||||
Args:
|
||||
accounts (List[Dict[str, int]]): A list of account dictionaries, each containing
|
||||
an 'expiaryDate' key with an epoch timestamp as its value.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, int]]: A list of accounts that have expired.
|
||||
"""
|
||||
# Get the current epoch timestamp
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
|
||||
# Filter accounts where the current date is greater than the expiryDate
|
||||
expired_accounts = [
|
||||
account for account in accounts
|
||||
if account['expiaryDate'] < current_timestamp
|
||||
]
|
||||
|
||||
return expired_accounts
|
||||
|
214
lib/reqs.py
214
lib/reqs.py
@ -1,107 +1,107 @@
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
def get_urls(base_url: str, auth: str) -> List[Dict[str, Any]]:
|
||||
"""Retrieve user account streams from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: A list of user account streams.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts/streams"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
return json.loads(response.text)
|
||||
|
||||
|
||||
def get_user_accounts(base_url: str, auth: str) -> List[Dict[str, Any]]:
|
||||
"""Retrieve user accounts from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: A list of user accounts with their expiration dates rendered.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
res_json = json.loads(response.text)
|
||||
|
||||
for account in res_json:
|
||||
account["expiaryDate_rendered"] = datetime.utcfromtimestamp(
|
||||
account["expiaryDate"]
|
||||
).strftime("%d/%m/%Y")
|
||||
|
||||
return res_json
|
||||
|
||||
|
||||
def delete_user_account(base_url: str, auth: str, stream: str, username: str) -> bool:
|
||||
"""Delete a user account from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
stream (str): The name of the stream associated with the user account.
|
||||
username (str): The username of the account to delete.
|
||||
|
||||
Returns:
|
||||
bool: True if the account was deleted successfully, False otherwise.
|
||||
"""
|
||||
url = f"{base_url}/deleteAccount"
|
||||
payload = {"stream": stream, "user": username}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
return "Deleted" in response.text
|
||||
|
||||
|
||||
def add_user_account(base_url: str, auth: str, username: str, password: str, stream: str) -> bool:
|
||||
"""Add a user account to the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
username (str): The username of the account to add.
|
||||
password (str): The password of the account to add.
|
||||
stream (str): The name of the stream associated with the user account.
|
||||
|
||||
Returns:
|
||||
bool: True if the account was added successfully, False otherwise.
|
||||
"""
|
||||
url = f"{base_url}/addAccount"
|
||||
payload = {"username": username, "password": password, "stream": stream}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
return "Added successfully" in response.text
|
||||
|
||||
|
||||
def get_user_accounts_count(base_url: str, auth: str) -> int:
|
||||
"""Get the count of user accounts from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
int: The count of user accounts.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts/count"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
res_json = json.loads(response.text)
|
||||
return res_json['count']
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
def get_urls(base_url: str, auth: str) -> List[Dict[str, Any]]:
|
||||
"""Retrieve user account streams from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: A list of user account streams.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts/streams"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
return json.loads(response.text)
|
||||
|
||||
|
||||
def get_user_accounts(base_url: str, auth: str) -> List[Dict[str, Any]]:
|
||||
"""Retrieve user accounts from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: A list of user accounts with their expiration dates rendered.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
res_json = json.loads(response.text)
|
||||
|
||||
for account in res_json:
|
||||
account["expiaryDate_rendered"] = datetime.utcfromtimestamp(
|
||||
account["expiaryDate"]
|
||||
).strftime("%d/%m/%Y")
|
||||
|
||||
return res_json
|
||||
|
||||
|
||||
def delete_user_account(base_url: str, auth: str, stream: str, username: str) -> bool:
|
||||
"""Delete a user account from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
stream (str): The name of the stream associated with the user account.
|
||||
username (str): The username of the account to delete.
|
||||
|
||||
Returns:
|
||||
bool: True if the account was deleted successfully, False otherwise.
|
||||
"""
|
||||
url = f"{base_url}/deleteAccount"
|
||||
payload = {"stream": stream, "user": username}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
return "Deleted" in response.text
|
||||
|
||||
|
||||
def add_user_account(base_url: str, auth: str, username: str, password: str, stream: str) -> bool:
|
||||
"""Add a user account to the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
username (str): The username of the account to add.
|
||||
password (str): The password of the account to add.
|
||||
stream (str): The name of the stream associated with the user account.
|
||||
|
||||
Returns:
|
||||
bool: True if the account was added successfully, False otherwise.
|
||||
"""
|
||||
url = f"{base_url}/addAccount"
|
||||
payload = {"username": username, "password": password, "stream": stream}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
return "Added successfully" in response.text
|
||||
|
||||
|
||||
def get_user_accounts_count(base_url: str, auth: str) -> int:
|
||||
"""Get the count of user accounts from the specified base URL.
|
||||
|
||||
Args:
|
||||
base_url (str): The base URL of the API.
|
||||
auth (str): The authorization token for accessing the API.
|
||||
|
||||
Returns:
|
||||
int: The count of user accounts.
|
||||
"""
|
||||
url = f"{base_url}/getUserAccounts/count"
|
||||
payload = {}
|
||||
headers = {"Authorization": f"Basic {auth}"}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
res_json = json.loads(response.text)
|
||||
return res_json['count']
|
||||
|
@ -1,15 +1,15 @@
|
||||
self.addEventListener('install', e => {
|
||||
// console.log('[Service Worker] Installed');
|
||||
});
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
// console.log('[Service Worker] Activated');
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', e => {
|
||||
// e.respondWith(
|
||||
// caches.match(e.request).then(res => {
|
||||
// return res || fetch(e.request);
|
||||
// })
|
||||
// );
|
||||
self.addEventListener('install', e => {
|
||||
// console.log('[Service Worker] Installed');
|
||||
});
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
// console.log('[Service Worker] Activated');
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', e => {
|
||||
// e.respondWith(
|
||||
// caches.match(e.request).then(res => {
|
||||
// return res || fetch(e.request);
|
||||
// })
|
||||
// );
|
||||
});
|
@ -1,30 +1,30 @@
|
||||
/* Base styles */
|
||||
html, body {
|
||||
height: 100%; /* Ensure the body and html elements fill the viewport height */
|
||||
margin: 0; /* Remove default margin */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1; /* Make the main content area grow to fill the available space */
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
}
|
||||
/* Base styles */
|
||||
html, body {
|
||||
height: 100%; /* Ensure the body and html elements fill the viewport height */
|
||||
margin: 0; /* Remove default margin */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1; /* Make the main content area grow to fill the available space */
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
@ -1,106 +1,106 @@
|
||||
<!-- templates/add_account.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Account - KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
<style>
|
||||
/* Hide the spinner by default */
|
||||
#loadingSpinner,
|
||||
#ocrLoadingSpinner {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Sub-navigation for Accounts -->
|
||||
<div class="bg-light py-2">
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">List Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/accounts/add">Add Account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mt-5">
|
||||
<h1>Add Account</h1>
|
||||
<div>
|
||||
<form action="/accounts/add" method="POST" onsubmit="showLoading()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="{{ username }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="text" class="form-control" id="password" name="password" value="{{ password }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stream">Stream Name</label>
|
||||
<input type="text" class="form-control" id="stream" name="stream" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="submitButton">
|
||||
<span class="spinner-border spinner-border-sm" id="loadingSpinner" role="status" aria-hidden="true"></span>
|
||||
<span id="buttonText">Add Account</span>
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<h4>Load Details Via OCR</h2>
|
||||
<form action="/OCRupload" method="POST" enctype="multipart/form-data" onsubmit="showLoadingOCR()">
|
||||
<div class="form-group">
|
||||
<label for="image">Select Image</label>
|
||||
<input type="file" class="form-control-file" id="image" name="image" accept="image/*" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" id="ocrButton">
|
||||
<span class="spinner-border spinner-border-sm" id="ocrLoadingSpinner" role="status" aria-hidden="true"></span>
|
||||
<span id="ocrButtonText">Load Details</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
<script>
|
||||
function showLoading() {
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
document.getElementById("loadingSpinner").style.display = "inline-block";
|
||||
document.getElementById("buttonText").textContent = "Working...";
|
||||
}
|
||||
function showLoadingOCR() {
|
||||
document.getElementById("ocrButton").disabled = true;
|
||||
document.getElementById("ocrLoadingSpinner").style.display = "inline-block";
|
||||
document.getElementById("ocrButtonText").textContent = "Processing...";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- templates/add_account.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Account - KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
<style>
|
||||
/* Hide the spinner by default */
|
||||
#loadingSpinner,
|
||||
#ocrLoadingSpinner {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Sub-navigation for Accounts -->
|
||||
<div class="bg-light py-2">
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">List Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/accounts/add">Add Account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mt-5">
|
||||
<h1>Add Account</h1>
|
||||
<div>
|
||||
<form action="/accounts/add" method="POST" onsubmit="showLoading()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="{{ username }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="text" class="form-control" id="password" name="password" value="{{ password }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stream">Stream Name</label>
|
||||
<input type="text" class="form-control" id="stream" name="stream" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="submitButton">
|
||||
<span class="spinner-border spinner-border-sm" id="loadingSpinner" role="status" aria-hidden="true"></span>
|
||||
<span id="buttonText">Add Account</span>
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<h4>Load Details Via OCR</h2>
|
||||
<form action="/OCRupload" method="POST" enctype="multipart/form-data" onsubmit="showLoadingOCR()">
|
||||
<div class="form-group">
|
||||
<label for="image">Select Image</label>
|
||||
<input type="file" class="form-control-file" id="image" name="image" accept="image/*" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" id="ocrButton">
|
||||
<span class="spinner-border spinner-border-sm" id="ocrLoadingSpinner" role="status" aria-hidden="true"></span>
|
||||
<span id="ocrButtonText">Load Details</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
<script>
|
||||
function showLoading() {
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
document.getElementById("loadingSpinner").style.display = "inline-block";
|
||||
document.getElementById("buttonText").textContent = "Working...";
|
||||
}
|
||||
function showLoadingOCR() {
|
||||
document.getElementById("ocrButton").disabled = true;
|
||||
document.getElementById("ocrLoadingSpinner").style.display = "inline-block";
|
||||
document.getElementById("ocrButtonText").textContent = "Processing...";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,93 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mt-5">
|
||||
<h1>Welcome {{ username }}!</h1>
|
||||
<br>
|
||||
<h2>You have {{ accounts }} active accounts</h2>
|
||||
<br>
|
||||
|
||||
{% if current_month_accounts %}
|
||||
<h3>Accounts Expiring Within 30 Days</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Stream Name</th>
|
||||
<th>Username</th>
|
||||
<th>Expiry Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in current_month_accounts %}
|
||||
<tr>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if expired_accounts %}
|
||||
<h3>Expired Accounts</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Stream Name</th>
|
||||
<th>Username</th>
|
||||
<th>Expiry Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in expired_accounts %}
|
||||
<tr>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mt-5">
|
||||
<h1>Welcome {{ username }}!</h1>
|
||||
<br>
|
||||
<h2>You have {{ accounts }} active accounts</h2>
|
||||
<br>
|
||||
|
||||
{% if current_month_accounts %}
|
||||
<h3>Accounts Expiring Within 30 Days</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Stream Name</th>
|
||||
<th>Username</th>
|
||||
<th>Expiry Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in current_month_accounts %}
|
||||
<tr>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if expired_accounts %}
|
||||
<h3>Expired Accounts</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Stream Name</th>
|
||||
<th>Username</th>
|
||||
<th>Expiry Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in expired_accounts %}
|
||||
<tr>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,80 +1,80 @@
|
||||
<!-- templates/index.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon-96x96.png') }}" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}" />
|
||||
<meta name="apple-mobile-web-app-title" content="kTvManager" />
|
||||
<link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h1>Welcome to KTV Manager</h1>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form action="/login" method="post" class="mt-3">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger mt-3">{{ error }}</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('{{ url_for("static", filename="service-worker.js") }}')
|
||||
// .then(reg => {
|
||||
// console.log('Service worker:', reg);
|
||||
// .catch(err => {
|
||||
// console.log('Service worker:', err);
|
||||
// });
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- templates/index.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon-96x96.png') }}" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}" />
|
||||
<meta name="apple-mobile-web-app-title" content="kTvManager" />
|
||||
<link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h1>Welcome to KTV Manager</h1>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form action="/login" method="post" class="mt-3">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger mt-3">{{ error }}</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('{{ url_for("static", filename="service-worker.js") }}')
|
||||
// .then(reg => {
|
||||
// console.log('Service worker:', reg);
|
||||
// .catch(err => {
|
||||
// console.log('Service worker:', err);
|
||||
// });
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,65 +1,65 @@
|
||||
<!-- templates/urls.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar (same as index.html) -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a> <!-- Link to the URLs page -->
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a> <!-- Link to the URLs page -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h2>URLs</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for url in urls %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td><a href="{{ url }}" target="_blank">{{ url }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- templates/urls.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar (same as index.html) -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts">Accounts</a> <!-- Link to the URLs page -->
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/urls">URLs</a> <!-- Link to the URLs page -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h2>URLs</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for url in urls %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td><a href="{{ url }}" target="_blank">{{ url }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,120 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.dataTables.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/accounts">Accounts</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/urls">URLs</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Sub-navigation for Accounts -->
|
||||
<div class="bg-light py-2">
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/accounts">List Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts/add">Add Account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h2>{{ username }}'s Accounts</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="accountsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- <th>#</th> -->
|
||||
<th>Username</th>
|
||||
<th>Stream</th>
|
||||
<th>Stream URL</th>
|
||||
<th>Expiry Date</th>
|
||||
<th>Password</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in user_accounts %}
|
||||
<tr>
|
||||
<!-- <td>{{ loop.index }}</td> -->
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td><a href="{{ account.streamURL }}" target="_blank">{{ account.streamURL }}</a></td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
<td>{{ account.password }}</td>
|
||||
<td>
|
||||
<form action="/accounts/delete" method="POST" style="display:inline;">
|
||||
<input type="hidden" name="stream" value="{{ account.stream }}">
|
||||
<input type="hidden" name="username" value="{{ account.username }}">
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this account?');">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Extend DataTables date sorting for DD/MM/YYYY format
|
||||
$.fn.dataTable.ext.type.order['date-eu-pre'] = function(data) {
|
||||
const parts = data.split('/');
|
||||
// Check if the data is in the correct format before processing
|
||||
if (parts.length === 3) {
|
||||
return new Date(parts[2], parts[1] - 1, parts[0]).getTime();
|
||||
}
|
||||
return data; // return as is if the format is unexpected
|
||||
};
|
||||
|
||||
// Initialize DataTable with custom sorting and default ordering by Expiry Date
|
||||
$('#accountsTable').DataTable({
|
||||
"searching": true,
|
||||
"ordering": true,
|
||||
"responsive": true,
|
||||
"order": [[3, 'asc']], // Default order by Expiry Date column in ascending order
|
||||
"columnDefs": [
|
||||
{ "type": "date-eu", "targets": 3 } // Use custom date-eu type for the date column
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KTVManager</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.dataTables.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">KTVManager</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/accounts">Accounts</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/urls">URLs</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Sub-navigation for Accounts -->
|
||||
<div class="bg-light py-2">
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/accounts">List Accounts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts/add">Add Account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main div class="container mt-5">
|
||||
<h2>{{ username }}'s Accounts</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="accountsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- <th>#</th> -->
|
||||
<th>Username</th>
|
||||
<th>Stream</th>
|
||||
<th>Stream URL</th>
|
||||
<th>Expiry Date</th>
|
||||
<th>Password</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in user_accounts %}
|
||||
<tr>
|
||||
<!-- <td>{{ loop.index }}</td> -->
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{{ account.stream }}</td>
|
||||
<td><a href="{{ account.streamURL }}" target="_blank">{{ account.streamURL }}</a></td>
|
||||
<td>{{ account.expiaryDate_rendered }}</td>
|
||||
<td>{{ account.password }}</td>
|
||||
<td>
|
||||
<form action="/accounts/delete" method="POST" style="display:inline;">
|
||||
<input type="hidden" name="stream" value="{{ account.stream }}">
|
||||
<input type="hidden" name="username" value="{{ account.username }}">
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this account?');">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p></p>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Extend DataTables date sorting for DD/MM/YYYY format
|
||||
$.fn.dataTable.ext.type.order['date-eu-pre'] = function(data) {
|
||||
const parts = data.split('/');
|
||||
// Check if the data is in the correct format before processing
|
||||
if (parts.length === 3) {
|
||||
return new Date(parts[2], parts[1] - 1, parts[0]).getTime();
|
||||
}
|
||||
return data; // return as is if the format is unexpected
|
||||
};
|
||||
|
||||
// Initialize DataTable with custom sorting and default ordering by Expiry Date
|
||||
$('#accountsTable').DataTable({
|
||||
"searching": true,
|
||||
"ordering": true,
|
||||
"responsive": true,
|
||||
"order": [[3, 'asc']], // Default order by Expiry Date column in ascending order
|
||||
"columnDefs": [
|
||||
{ "type": "date-eu", "targets": 3 } // Use custom date-eu type for the date column
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user