1182 lines
49 KiB
Python
1182 lines
49 KiB
Python
import os
|
||
import sys
|
||
import requests
|
||
import base64
|
||
import platform
|
||
|
||
from PyQt6.QtWidgets import (
|
||
QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton,
|
||
QToolButton, QSizePolicy, QStackedWidget, QSpacerItem, QTextEdit, QScrollArea,
|
||
QMessageBox, QHBoxLayout, QTabWidget, QFileDialog, QComboBox, QDialog
|
||
)
|
||
from PyQt6.QtCore import Qt, QEvent, QTime, pyqtSlot, QTimer, QUrl, QObject, QDateTime
|
||
from PyQt6.QtGui import QPalette, QColor, QPixmap, QFont
|
||
from PyQt6.QtWebChannel import QWebChannel
|
||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||
|
||
|
||
# Placeholder new screens to navigate on button click
|
||
class JSBridge(QObject):
|
||
@pyqtSlot(str, str, str)
|
||
def saveFile(self, base64_data, filename, source):
|
||
try:
|
||
if base64_data.startswith("data:"):
|
||
base64_data = base64_data.split(",", 1)[1]
|
||
data = base64.b64decode(base64_data)
|
||
|
||
path, _ = QFileDialog.getSaveFileName(None, f"Save {source.title()} File", filename, "All Files (*)")
|
||
if path:
|
||
with open(path, 'wb') as f:
|
||
f.write(data)
|
||
QMessageBox.information(None, f"{source.title()} Download", f"File saved to:\n{path}")
|
||
if platform.system() == "Windows":
|
||
os.startfile(path)
|
||
elif platform.system() == "Darwin":
|
||
os.system(f"open '{path}'")
|
||
else:
|
||
os.system(f"xdg-open '{path}'")
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(None, "Save Failed", str(e))
|
||
|
||
|
||
class ProductionDisplayScreen(QWidget):
|
||
def __init__(self, stacked, url="https://pds.iotsignin.com/admin"):
|
||
super().__init__()
|
||
self.stacked = stacked
|
||
self.url = url
|
||
self.setObjectName("ProductionDisplayScreen")
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
self.channel = QWebChannel()
|
||
self.js_bridge = JSBridge()
|
||
self.channel.registerObject("pyBridge", self.js_bridge)
|
||
|
||
# Header
|
||
header = QWidget()
|
||
hl = QHBoxLayout(header)
|
||
hl.setContentsMargins(8, 8, 8, 8)
|
||
header.setStyleSheet("background-color: #e6f2ff;")
|
||
|
||
# Logo
|
||
logo = QLabel()
|
||
pix = QPixmap(
|
||
r"C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png")
|
||
logo.setPixmap(
|
||
pix.scaled(90, 70, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
|
||
hl.addWidget(logo)
|
||
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
||
|
||
# Title
|
||
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS")
|
||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
title.setStyleSheet("font-weight:bold; font-size:28px;")
|
||
hl.addWidget(title)
|
||
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
||
|
||
# Back button
|
||
self.btn_back = QPushButton("Back")
|
||
self.btn_back.setStyleSheet("""
|
||
QPushButton {
|
||
background-color: #c2185b;
|
||
color: white;
|
||
border: none;
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #34495E;
|
||
}
|
||
QPushButton:pressed {
|
||
background-color: #1F2D3D;
|
||
}
|
||
""")
|
||
self.btn_back.clicked.connect(self.go_back)
|
||
hl.addWidget(self.btn_back)
|
||
|
||
# Navigation buttons
|
||
btn_style = """
|
||
QPushButton {
|
||
background-color: #c2185b;
|
||
color: white;
|
||
border: none;
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #34495E;
|
||
}
|
||
QPushButton:pressed {
|
||
background-color: #1F2D3D;
|
||
}
|
||
"""
|
||
self.btn_prev = QPushButton("Previous")
|
||
self.btn_refresh = QPushButton("Refresh")
|
||
self.btn_next = QPushButton("Next")
|
||
self.btn_full = QPushButton("Full Screen")
|
||
self.btn_dup = QPushButton("Duplicate")
|
||
self.btn_del = QPushButton("Delete")
|
||
self.tab_dropdown = QComboBox()
|
||
|
||
for b in [self.btn_prev, self.btn_refresh, self.btn_next, self.btn_full, self.btn_dup, self.btn_del]:
|
||
b.setStyleSheet(btn_style)
|
||
hl.addWidget(b)
|
||
hl.addWidget(self.tab_dropdown)
|
||
|
||
# Tabbed WebView
|
||
self.tabs = QTabWidget()
|
||
self.tabs.setTabsClosable(False)
|
||
self.add_tab(self.url)
|
||
|
||
self.tabs.setStyleSheet("""
|
||
QTabWidget::pane { background-color: #ffe6e6; }
|
||
QTabBar::tab {
|
||
background: #f8d7da;
|
||
padding: 6px;
|
||
border-radius: 4px;
|
||
}
|
||
QTabBar::tab:selected {
|
||
background: #e6e6fa;
|
||
font-weight: bold;
|
||
}
|
||
""")
|
||
|
||
# Connections
|
||
self.btn_prev.clicked.connect(lambda: self.notify("Going to previous page...", self.go_prev))
|
||
self.btn_refresh.clicked.connect(lambda: self.notify("Refreshing...", self.go_refresh))
|
||
self.btn_next.clicked.connect(lambda: self.notify("Going to next page...", self.go_next))
|
||
self.btn_full.clicked.connect(self.toggle_fullscreen)
|
||
self.btn_dup.clicked.connect(self.duplicate_tab)
|
||
self.btn_del.clicked.connect(self.delete_tab)
|
||
self.tab_dropdown.currentIndexChanged.connect(self.switch_tab)
|
||
|
||
# Layout
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.addWidget(header)
|
||
layout.addWidget(self.tabs)
|
||
|
||
def add_tab(self, url):
|
||
webview = QWebEngineView()
|
||
webview.page().setWebChannel(self.channel)
|
||
index = self.tabs.addTab(webview, "Loading…")
|
||
self.tab_dropdown.addItem("Loading…")
|
||
self.tabs.setCurrentIndex(index)
|
||
self.tab_dropdown.setCurrentIndex(index)
|
||
|
||
webview.page().titleChanged.connect(lambda title, w=webview: self.update_tab_title(w, title))
|
||
webview.page().loadFinished.connect(lambda ok, w=webview: self.inject_js(w) if ok else None)
|
||
webview.load(QUrl(url))
|
||
|
||
def update_tab_title(self, webview, title):
|
||
i = self.tabs.indexOf(webview)
|
||
if i != -1:
|
||
self.tabs.setTabText(i, title or "New Tab")
|
||
self.tab_dropdown.setItemText(i, title or "New Tab")
|
||
|
||
def notify(self, message, callback):
|
||
dlg = QDialog(self)
|
||
dlg.setWindowFlags(Qt.WindowType.FramelessWindowHint)
|
||
dlg.setStyleSheet("""
|
||
QDialog { background-color: #34495E; border: 2px solid #2C3E50; border-radius: 10px; }
|
||
QLabel { color: white; padding: 15px; font-size: 14px; }
|
||
""")
|
||
lbl = QLabel(message)
|
||
lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
v = QVBoxLayout(dlg)
|
||
v.addWidget(lbl)
|
||
dlg.resize(250, 80)
|
||
dlg.move(self.geometry().center().x() - 125, self.geometry().center().y() - 40)
|
||
dlg.show()
|
||
QTimer.singleShot(1000, lambda: (dlg.close(), callback()))
|
||
|
||
def current_webview(self):
|
||
return self.tabs.currentWidget()
|
||
|
||
def go_prev(self):
|
||
view = self.current_webview()
|
||
if view and view.history().canGoBack():
|
||
view.back()
|
||
|
||
def go_refresh(self):
|
||
view = self.current_webview()
|
||
if view: view.reload()
|
||
|
||
def go_next(self):
|
||
view = self.current_webview()
|
||
if view and view.history().canGoForward():
|
||
view.forward()
|
||
|
||
def toggle_fullscreen(self):
|
||
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
|
||
|
||
def duplicate_tab(self):
|
||
view = self.current_webview()
|
||
if view:
|
||
self.add_tab(view.url().toString())
|
||
|
||
def delete_tab(self):
|
||
if self.tabs.count() > 1:
|
||
index = self.tabs.currentIndex()
|
||
self.tabs.removeTab(index)
|
||
self.tab_dropdown.removeItem(index)
|
||
|
||
def switch_tab(self, index):
|
||
self.tabs.setCurrentIndex(index)
|
||
|
||
def go_back(self):
|
||
for i in range(self.stacked.count()):
|
||
widget = self.stacked.widget(i)
|
||
if isinstance(widget, SelectorScreen):
|
||
self.stacked.setCurrentWidget(widget)
|
||
return
|
||
|
||
def inject_js(self, webview):
|
||
js_code = """
|
||
(function () {
|
||
if (window._fileDownloadInjected) return;
|
||
window._fileDownloadInjected = true;
|
||
|
||
let _downloadInProgress = false;
|
||
|
||
function getFileUrlAndType(el) {
|
||
let href = el.getAttribute("href") || el.getAttribute("data-url") || "";
|
||
|
||
if (!href) {
|
||
const inner = el.querySelector("a[href]");
|
||
if (inner) href = inner.getAttribute("href") || "";
|
||
}
|
||
|
||
let filename = "download.csv";
|
||
if (href) {
|
||
const parts = href.split('/');
|
||
filename = parts[parts.length - 1].split('?')[0] || filename;
|
||
}
|
||
|
||
const isExport = href.includes("/exports") || el.textContent.toLowerCase().includes("export");
|
||
const source = isExport ? "export" : "import";
|
||
|
||
return { href, filename, source };
|
||
}
|
||
|
||
function startDownload(e, href, filename, source) {
|
||
if (_downloadInProgress) return;
|
||
_downloadInProgress = true;
|
||
|
||
e.preventDefault();
|
||
|
||
fetch(href, { credentials: 'include' })
|
||
.then(res => {
|
||
if (!res.ok) throw new Error("Failed: " + res.status);
|
||
return res.blob();
|
||
})
|
||
.then(blob => {
|
||
const reader = new FileReader();
|
||
reader.onloadend = () => {
|
||
const base64 = reader.result;
|
||
if (window.pyBridge && window.pyBridge.saveFile) {
|
||
window.pyBridge.saveFile(base64, filename, source);
|
||
} else {
|
||
alert("❌ pyBridge not available");
|
||
}
|
||
_downloadInProgress = false;
|
||
};
|
||
reader.readAsDataURL(blob);
|
||
})
|
||
.catch(err => {
|
||
alert("❌ Download error: " + err.message);
|
||
_downloadInProgress = false;
|
||
});
|
||
}
|
||
|
||
document.addEventListener("click", function (e) {
|
||
const el = e.target.closest("a,button");
|
||
if (!el) return;
|
||
|
||
const { href, filename, source } = getFileUrlAndType(el);
|
||
|
||
if (href && (href.endsWith(".csv") || href.endsWith(".xlsx") || href.includes("/exports") || href.startsWith("blob:"))) {
|
||
startDownload(e, href, filename, source);
|
||
}
|
||
}, true);
|
||
|
||
const origCreateObjectURL = URL.createObjectURL;
|
||
URL.createObjectURL = function (blob) {
|
||
if (_downloadInProgress) return origCreateObjectURL(blob);
|
||
_downloadInProgress = true;
|
||
|
||
try {
|
||
const reader = new FileReader();
|
||
reader.onloadend = function () {
|
||
const base64 = reader.result;
|
||
if (window.pyBridge && window.pyBridge.saveFile) {
|
||
window.pyBridge.saveFile(base64, "blob_file.csv", "import");
|
||
}
|
||
_downloadInProgress = false;
|
||
};
|
||
reader.readAsDataURL(blob);
|
||
} catch (e) {
|
||
console.error("Blob hook failed", e);
|
||
_downloadInProgress = false;
|
||
}
|
||
|
||
return origCreateObjectURL(blob);
|
||
};
|
||
|
||
function waitForBridge() {
|
||
if (typeof QWebChannel === 'undefined') return setTimeout(waitForBridge, 100);
|
||
new QWebChannel(qt.webChannelTransport, function (channel) {
|
||
window.pyBridge = channel.objects.pyBridge;
|
||
});
|
||
}
|
||
waitForBridge();
|
||
})();
|
||
"""
|
||
|
||
webview.page().runJavaScript(f"""
|
||
var script = document.createElement('script');
|
||
script.src = 'qrc:///qtwebchannel/qwebchannel.js';
|
||
script.onload = function() {{
|
||
{js_code}
|
||
}};
|
||
document.head.appendChild(script);
|
||
""")
|
||
|
||
|
||
class BotScreen(QWidget):
|
||
def __init__(self, stacked, module_name=""):
|
||
super().__init__()
|
||
self.stacked = stacked
|
||
self.module_name = module_name
|
||
self.charts = []
|
||
self.plants = []
|
||
self.lines = []
|
||
self.filters = []
|
||
|
||
self.awaiting_chart_choice = False
|
||
self.awaiting_plant_choice = False
|
||
self.awaiting_line_choice = False
|
||
self.awaiting_order_input = False
|
||
self.awaiting_filter_choice = False
|
||
|
||
self.selected_chart = None
|
||
self.selected_plant = None
|
||
self.selected_line = None
|
||
self.selected_order = None
|
||
self.selected_filter = None
|
||
|
||
self.init_ui()
|
||
|
||
if self.module_name:
|
||
self.load_chart_data()
|
||
|
||
def set_module_name(self, name):
|
||
self.module_name = name
|
||
self.clear_chat()
|
||
self.load_chart_data()
|
||
|
||
def init_ui(self):
|
||
main_layout = QVBoxLayout()
|
||
top_bar = QHBoxLayout()
|
||
logo = QLabel()
|
||
logo.setPixmap(QPixmap(
|
||
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png'
|
||
).scaled(40, 40, Qt.AspectRatioMode.KeepAspectRatio))
|
||
top_bar.addWidget(logo)
|
||
|
||
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS \n CRI BOT ASSISTANT")
|
||
title.setStyleSheet("font-size: 16px; font-weight: bold; color: black; padding: 10px; border-radius: 8px;")
|
||
top_bar.addWidget(title)
|
||
|
||
top_bar.addStretch()
|
||
back_btn = QPushButton("Back")
|
||
back_btn.clicked.connect(self.go_back)
|
||
top_bar.addWidget(back_btn)
|
||
|
||
refresh_btn = QPushButton("Refresh")
|
||
refresh_btn.clicked.connect(self.load_chart_data)
|
||
top_bar.addWidget(refresh_btn)
|
||
main_layout.addLayout(top_bar)
|
||
|
||
self.chart_info = QLabel("Waiting for chart data...")
|
||
self.chart_info.setWordWrap(True)
|
||
self.chart_info.setStyleSheet(
|
||
"background: white; color: black; font-size: 15px; padding: 14px; border-radius: 10px")
|
||
main_layout.addWidget(self.chart_info)
|
||
|
||
self.chat_area = QTextEdit()
|
||
self.chat_area.setReadOnly(True)
|
||
self.chat_area.setStyleSheet("background-color: white; color: black; padding: 10px; font-size: 14px;")
|
||
main_layout.addWidget(self.chat_area)
|
||
|
||
input_layout = QHBoxLayout()
|
||
|
||
self.user_input = QLineEdit()
|
||
self.user_input.setPlaceholderText("Type a number to select...")
|
||
input_layout.addWidget(self.user_input)
|
||
|
||
send_btn = QPushButton("Send")
|
||
send_btn.setStyleSheet(
|
||
"padding: 8px; background: #2e7d32; color: white; border-radius: 8px;")
|
||
send_btn.clicked.connect(self.send_message)
|
||
input_layout.addWidget(send_btn)
|
||
main_layout.addLayout(input_layout)
|
||
|
||
self.setLayout(main_layout)
|
||
|
||
def load_chart_data(self):
|
||
self.chart_info.setText("Loading chart data...")
|
||
url = 'https://pds.iotsignin.com/api/get/modulechart-name/data'
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"module-name": self.module_name
|
||
}
|
||
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
data = response.json()
|
||
status = data.get("status_code", "UNKNOWN")
|
||
desc = data.get("status_description", [])
|
||
self.chart_info.setText(f"API Status: {status}")
|
||
self.chat_area.append(f"<b>HTTP:</b> {response.status_code}")
|
||
self.chat_area.append(f"<b>Bot Module:</b> {self.module_name}")
|
||
if isinstance(desc, list) and desc:
|
||
self.charts = desc
|
||
else:
|
||
self.charts = [
|
||
"Production Line Count",
|
||
"Production Hourly Count",
|
||
"Production Line Stop Count",
|
||
"Production Order Count"
|
||
]
|
||
|
||
self.awaiting_chart_choice = True
|
||
self.show_choice_menu("chart", self.charts)
|
||
except Exception as e:
|
||
self.chart_info.setText("Failed to load chart data.")
|
||
self.chat_area.append(f"<b>Bot:</b> Error: {e}")
|
||
|
||
def show_choice_menu(self, item, items_list):
|
||
label_map = {
|
||
"chart": "chart",
|
||
"plant": "plant",
|
||
"line": "line",
|
||
"order": "production order",
|
||
"filter": "date filter"
|
||
}
|
||
|
||
lines = [f"{i + 1}. {name}" for i, name in enumerate(items_list)]
|
||
menu_txt = f"<b>Bot:</b> Please select a {label_map[item]}:<br>" + "<br>".join(lines)
|
||
self.chat_area.append(menu_txt)
|
||
|
||
def send_message(self):
|
||
msg = self.user_input.text().strip()
|
||
self.user_input.clear()
|
||
|
||
if self.awaiting_chart_choice:
|
||
if msg.isdigit():
|
||
idx = int(msg) - 1
|
||
if 0 <= idx < len(self.charts):
|
||
self.selected_chart = self.charts[idx]
|
||
self.awaiting_chart_choice = False
|
||
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_chart}</b>")
|
||
self.fetch_and_display_plants()
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.charts)}.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.charts)}).")
|
||
return
|
||
|
||
if self.awaiting_plant_choice:
|
||
if msg.isdigit():
|
||
idx = int(msg) - 1
|
||
if 0 <= idx < len(self.plants):
|
||
self.selected_plant = self.plants[idx]
|
||
self.awaiting_plant_choice = False
|
||
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_plant}</b>")
|
||
self.fetch_and_display_lines()
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.plants)}.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.plants)}).")
|
||
return
|
||
|
||
if self.awaiting_line_choice:
|
||
if msg.isdigit():
|
||
idx = int(msg) - 1
|
||
if 0 <= idx < len(self.lines):
|
||
self.selected_line = self.lines[idx]
|
||
self.awaiting_line_choice = False
|
||
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_line}</b>")
|
||
chart_raw = self.selected_chart
|
||
chart_normalized = chart_raw.strip().lower() if chart_raw else ""
|
||
|
||
if chart_normalized == "production order count":
|
||
print(f"[DEBUG] self.selected_chart (raw): '{self.selected_chart}'")
|
||
self.awaiting_order_input = True
|
||
self.chat_area.append("<b>Bot:</b> Please enter the production order number:")
|
||
else:
|
||
self.fetch_and_display_filters()
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.lines)}.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.lines)}).")
|
||
return
|
||
|
||
if hasattr(self, 'awaiting_order_input') and self.awaiting_order_input:
|
||
order = msg.strip()
|
||
if not order.isdigit():
|
||
self.chat_area.append("<b>Bot:</b> ❌ Please enter a valid production order number (numbers only).")
|
||
else:
|
||
self.selected_order = order
|
||
self.awaiting_order_input = False
|
||
self.chat_area.append(f"<b>Bot:</b> ✅ Production order set: <b>{self.selected_order}</b>")
|
||
self.fetch_and_display_filters()
|
||
return
|
||
|
||
if self.awaiting_filter_choice:
|
||
if msg.isdigit():
|
||
idx = int(msg) - 1
|
||
if 0 <= idx < len(self.filters):
|
||
self.selected_filter = self.filters[idx]
|
||
self.awaiting_filter_choice = False
|
||
self.chat_area.append(f"<b>Bot:</b> ✅ You selected filter: <b>{self.selected_filter}</b>")
|
||
now = QDateTime.currentDateTime()
|
||
fmt_date = now.toString("dddd, MMMM d, yyyy, hh:mm AP")
|
||
self.chat_area.append(f"<b>Current date:</b> {fmt_date}")
|
||
|
||
selected = self.selected_chart.strip().lower()
|
||
if selected == "production hourly count":
|
||
self.fetch_and_display_hourly_count()
|
||
elif selected == "production line stop count":
|
||
self.fetch_and_display_production_line_stop_count()
|
||
elif selected == "production order count":
|
||
self.fetch_and_display_production_order_count()
|
||
else:
|
||
self.fetch_and_display_production_count()
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.filters)}.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a valid number (1–{len(self.filters)}).")
|
||
return
|
||
|
||
def fetch_and_display_plants(self):
|
||
url = 'https://pds.iotsignin.com/api/get/moduleplant-name/data'
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"plant-name": "Plant List"
|
||
}
|
||
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
data = response.json()
|
||
if data.get("status_code") == "SUCCESS":
|
||
plants = data.get("status_description", [])
|
||
if plants:
|
||
self.plants = plants
|
||
self.awaiting_plant_choice = True
|
||
self.show_choice_menu("plant", plants)
|
||
else:
|
||
self.chat_area.append("<b>Bot:</b> ❌ No plants found.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load plants: {e}")
|
||
|
||
def fetch_and_display_lines(self):
|
||
url = 'https://pds.iotsignin.com/api/get/module-plantline-name/data'
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"plant-name": self.selected_plant,
|
||
"chart-name": self.selected_chart
|
||
}
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
data = response.json()
|
||
if data.get("status_code") == "SUCCESS":
|
||
lines = data.get("status_description", [])
|
||
if lines:
|
||
self.lines = lines
|
||
self.awaiting_line_choice = True
|
||
self.show_choice_menu("line", lines)
|
||
else:
|
||
self.chat_area.append("<b>Bot:</b> ❌ No lines found for the selected plant.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load lines: {e}")
|
||
|
||
def fetch_and_display_filters(self):
|
||
url = 'https://pds.iotsignin.com/api/get/module-line-filter-name/data'
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"line-name": self.selected_line
|
||
}
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
data = response.json()
|
||
if data.get("status_code") == "SUCCESS":
|
||
filters = data.get("status_description", [])
|
||
if filters:
|
||
self.filters = filters
|
||
self.awaiting_filter_choice = True
|
||
self.show_choice_menu("filter", filters)
|
||
else:
|
||
self.chat_area.append("<b>Bot:</b> ❌ No date filters found for the selected line.")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load date filters: {e}")
|
||
|
||
def fetch_and_display_production_count(self):
|
||
url = 'https://pds.iotsignin.com/api/get/module-filter-value/data'
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"module-name": self.module_name,
|
||
"chart-name": self.selected_chart,
|
||
"plant-name": self.selected_plant,
|
||
"line-name": self.selected_line,
|
||
"filter-name": self.selected_filter
|
||
}
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
data = response.json()
|
||
if data.get("status_code") == "SUCCESS":
|
||
result = data.get("status_description")
|
||
if isinstance(result, dict):
|
||
self.chat_area.append(f"<b>Bot:</b> 📊 <b>Production Counts (All Lines):</b>")
|
||
for ln, count in result.items():
|
||
self.chat_area.append(f"{ln}: {count}")
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> 📈 <b>Production Count:</b> {result}")
|
||
else:
|
||
self.chat_area.append(
|
||
f"<b>Bot:</b> ❌ Error getting production count: {data.get('status_description', 'Unknown')}")
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load production count: {e}")
|
||
|
||
API_TOKEN = "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE="
|
||
|
||
def fetch_and_display_hourly_count(self):
|
||
url = "https://pds.iotsignin.com/api/get/module-filter-value/data"
|
||
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"Accept": "application/json",
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||
"module-name": "Production Dashboard",
|
||
"chart-name": "Production Hourly Count",
|
||
"plant-name": self.selected_plant,
|
||
"line-name": self.selected_line,
|
||
"filter-name": self.selected_filter
|
||
}
|
||
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
|
||
print("📡 [DEBUG] HTTP Status:", response.status_code)
|
||
print("📡 [DEBUG] Raw API Response:\n", response.text)
|
||
print("-" * 50)
|
||
|
||
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
|
||
|
||
if not response.text.strip():
|
||
print("[DEBUG] ❌ Empty response received.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ API returned no data.")
|
||
return
|
||
|
||
try:
|
||
data = response.json()
|
||
except Exception as json_err:
|
||
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse data: {json_err}")
|
||
return
|
||
|
||
if data.get("status_code") == "SUCCESS":
|
||
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
|
||
self.chat_area.append(
|
||
f"<b>📊 Production Hourly Count ({self.selected_filter})</b><br><b>Current date:</b> {now}<br><br>")
|
||
for dataset in data.get("datasets", []):
|
||
label = dataset.get("label", "Data")
|
||
rows = ""
|
||
for lbl, val in zip(data.get("labels", []), dataset.get("data", [])):
|
||
rows += f"{lbl} ➤ <b>{val}</b><br>"
|
||
self.chat_area.append(f"<b>{label}:</b><br>{rows}")
|
||
print("[DEBUG] ✅ Hourly count displayed successfully.")
|
||
else:
|
||
req_status = data.get("status_code")
|
||
print(f"[DEBUG] ❌ API returned status {req_status}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ API returned status <code>{req_status}</code>")
|
||
|
||
except requests.exceptions.HTTPError as errh:
|
||
print("[DEBUG] ❌ HTTP Error:", errh)
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP Error: {errh}")
|
||
|
||
except requests.exceptions.ConnectionError:
|
||
print("[DEBUG] ❌ Connection Error.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ Connection error.")
|
||
|
||
except requests.exceptions.Timeout:
|
||
print("[DEBUG] ❌ Timeout Error.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ Request timed out.")
|
||
|
||
except Exception as e:
|
||
print(f"[DEBUG] ❌ General Error: {e}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Unexpected Error: {e}")
|
||
|
||
def fetch_and_display_production_line_stop_count(self):
|
||
url = "https://pds.iotsignin.com/api/get/module-production-linestop/data"
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"module-name": "Production Dashboard",
|
||
"chart-name": "Production Line Stop",
|
||
"plant-name": self.selected_plant,
|
||
"line-name": self.selected_line,
|
||
"filter-name": self.selected_filter
|
||
}
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
|
||
if response.status_code != 200:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP error {response.status_code}")
|
||
return
|
||
if not response.text.strip():
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ No response from API.")
|
||
return
|
||
try:
|
||
data = response.json()
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse API response: {e}")
|
||
return
|
||
current_date = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
|
||
if data.get("status_code") == "SUCCESS":
|
||
labels = data.get("labels", [])
|
||
values = data.get("data", [])
|
||
message = f"<b>Production Line Stop Counts</b><br><b>Current date:</b> {current_date}<br><br>"
|
||
for label, value in zip(labels, values):
|
||
message += f"<b>{label}:</b> {value}<br>"
|
||
self.chat_area.append(message)
|
||
else:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ No data found for this selection.")
|
||
except Exception as e:
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Error fetching report: {e}")
|
||
|
||
def fetch_and_display_production_order_count(self):
|
||
url = "https://pds.iotsignin.com/api/get/module-production-order/data"
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"module-name": "Production Dashboard",
|
||
"chart-name": "Production Order Count",
|
||
"plant-name": self.selected_plant,
|
||
"line-name": self.selected_line,
|
||
"production-order": self.selected_order,
|
||
"filter-name": self.selected_filter
|
||
}
|
||
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
|
||
print("📡 [DEBUG] HTTP Status:", response.status_code)
|
||
print("📡 [DEBUG] Raw API Response:\n", response.text)
|
||
print("-" * 50)
|
||
|
||
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
|
||
|
||
if not response.text.strip():
|
||
print("[DEBUG] ❌ Empty API response received.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ API returned no data.")
|
||
return
|
||
|
||
try:
|
||
data = response.json()
|
||
except Exception as json_err:
|
||
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse data: {json_err}")
|
||
return
|
||
|
||
status = data.get("status_code")
|
||
if status == "SUCCESS":
|
||
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
|
||
self.chat_area.append(
|
||
f"<b>Production Order Count ({self.selected_filter})</b><br><b>Current date:</b> {now}<br><br>")
|
||
datasets = data.get("datasets", [])
|
||
|
||
for dataset in datasets:
|
||
label = dataset.get("label", "Data")
|
||
self.chat_area.append(f"<b>{label}:</b>")
|
||
if isinstance(dataset.get("data"), dict):
|
||
for timeslot, count in dataset.get("data", {}).items():
|
||
self.chat_area.append(f"{timeslot}: <b>{count}</b><br>")
|
||
elif isinstance(dataset.get("data"), list):
|
||
labels = data.get("labels", [])
|
||
for idx, value in enumerate(dataset.get("data")):
|
||
label_text = labels[idx] if idx < len(labels) else f"Data {idx + 1}"
|
||
self.chat_area.append(f"{label_text}: <b>{value}</b><br>")
|
||
else:
|
||
self.chat_area.append(f"Data: <b>{dataset.get('data')}</b>")
|
||
print("[DEBUG] ✅ Production order count handled/displayed.")
|
||
else:
|
||
print(f"[DEBUG] ❌ API status_code not SUCCESS: {status}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ API returned status <code>{status}</code>")
|
||
|
||
except requests.exceptions.HTTPError as errh:
|
||
print("[DEBUG] ❌ HTTP Error:", errh)
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP Error: {errh}")
|
||
|
||
except requests.exceptions.ConnectionError:
|
||
print("[DEBUG] ❌ Connection Error.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ Connection error.")
|
||
|
||
except requests.exceptions.Timeout:
|
||
print("[DEBUG] ❌ Timeout Error.")
|
||
self.chat_area.append("<b>Bot:</b> ❌ Request timed out.")
|
||
|
||
except Exception as e:
|
||
print(f"[DEBUG] ❌ General Error: {e}")
|
||
self.chat_area.append(f"<b>Bot:</b> ❌ Unexpected Error: {e}")
|
||
|
||
def clear_chat(self):
|
||
self.chat_area.clear()
|
||
|
||
def go_back(self):
|
||
for i in range(self.stacked.count()):
|
||
widget = self.stacked.widget(i)
|
||
if isinstance(widget, SelectorScreen):
|
||
self.stacked.setCurrentWidget(widget)
|
||
return
|
||
|
||
|
||
class SelectorScreen(QWidget):
|
||
def __init__(self, stacked, username=None):
|
||
super().__init__()
|
||
self.stacked = stacked
|
||
self.username = username or "User"
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
# Set light blue background
|
||
palette = self.palette()
|
||
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
|
||
self.setPalette(palette)
|
||
self.setAutoFillBackground(True)
|
||
|
||
# Set Times New Roman font for entire screen
|
||
self.setFont(QFont("Times New Roman"))
|
||
|
||
self.layout = QVBoxLayout()
|
||
self.layout.setContentsMargins(40, 30, 40, 30)
|
||
self.layout.setSpacing(15)
|
||
self.layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
|
||
|
||
# CRI red logo at top
|
||
self.cri_logo = QLabel()
|
||
cri_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
|
||
self.cri_logo.setPixmap(QPixmap(cri_logo_path).scaledToWidth(150, Qt.TransformationMode.SmoothTransformation))
|
||
self.cri_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(self.cri_logo)
|
||
|
||
# Title
|
||
title_label = QLabel("SELECTOR SCREEN")
|
||
title_label.setFont(QFont("Times New Roman", 18, QFont.Weight.Bold))
|
||
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(title_label)
|
||
|
||
# Welcome username label
|
||
welcome_label = QLabel(f"Welcome, {self.username}!")
|
||
welcome_label.setFont(QFont("Times New Roman", 14))
|
||
welcome_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(welcome_label)
|
||
|
||
# Instruction label
|
||
instruction_label = QLabel("Choose menu to use")
|
||
instruction_label.setFont(QFont("Times New Roman", 12))
|
||
instruction_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(instruction_label)
|
||
|
||
# Production Display Section
|
||
prod_display_title = QLabel("PRODUCTION DISPLAY WEBSITE")
|
||
prod_display_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
|
||
prod_display_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(prod_display_title)
|
||
|
||
self.production_display_btn = QPushButton("Production Display Website")
|
||
self.production_display_btn.setFont(QFont("Times New Roman", 12))
|
||
self.production_display_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
self.production_display_btn.setStyleSheet(
|
||
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
|
||
self.production_display_btn.clicked.connect(self.open_production_display)
|
||
self.layout.addWidget(self.production_display_btn)
|
||
|
||
# Dashboards Section
|
||
dashboards_title = QLabel("DASHBOARDS")
|
||
dashboards_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
|
||
dashboards_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(dashboards_title)
|
||
|
||
self.production_dashboard_btn = QPushButton("Production Dashboard")
|
||
self.production_dashboard_btn.setFont(QFont("Times New Roman", 12))
|
||
self.production_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
self.production_dashboard_btn.setStyleSheet(
|
||
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
|
||
self.production_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Production Dashboard"))
|
||
self.layout.addWidget(self.production_dashboard_btn)
|
||
|
||
self.invoice_dashboard_btn = QPushButton("Invoice Dashboard")
|
||
self.invoice_dashboard_btn.setFont(QFont("Times New Roman", 12))
|
||
self.invoice_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
self.invoice_dashboard_btn.setStyleSheet(
|
||
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
|
||
self.invoice_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Invoice Dashboard"))
|
||
self.layout.addWidget(self.invoice_dashboard_btn)
|
||
|
||
# Spacer to push the logo down
|
||
self.layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding))
|
||
|
||
# CRI banner logo at bottom
|
||
self.cri_banner_logo = QLabel()
|
||
banner_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
|
||
self.cri_banner_logo.setPixmap(
|
||
QPixmap(banner_logo_path).scaledToWidth(350, Qt.TransformationMode.SmoothTransformation))
|
||
self.cri_banner_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.layout.addWidget(self.cri_banner_logo)
|
||
|
||
self.setLayout(self.layout)
|
||
|
||
def open_production_display(self):
|
||
# Check if screen already exists
|
||
for i in range(self.stacked.count()):
|
||
widget = self.stacked.widget(i)
|
||
if isinstance(widget, ProductionDisplayScreen):
|
||
self.stacked.setCurrentWidget(widget)
|
||
return
|
||
|
||
# Otherwise, add new screen
|
||
display_screen = ProductionDisplayScreen(self.stacked)
|
||
self.stacked.addWidget(display_screen)
|
||
self.stacked.setCurrentWidget(display_screen)
|
||
|
||
def open_dashboard(self, dashboard_type):
|
||
# Check if screen already exists
|
||
for i in range(self.stacked.count()):
|
||
widget = self.stacked.widget(i)
|
||
if isinstance(widget, BotScreen) and widget.module_name == dashboard_type:
|
||
self.stacked.setCurrentWidget(widget)
|
||
return
|
||
|
||
# Otherwise, create new screen
|
||
bot_screen = BotScreen(self.stacked, dashboard_type)
|
||
self.stacked.addWidget(bot_screen)
|
||
self.stacked.setCurrentWidget(bot_screen)
|
||
|
||
def show_error(self, msg):
|
||
error = QLabel(f"❌ {msg}")
|
||
error.setStyleSheet("color: red;")
|
||
self.layout.addWidget(error)
|
||
|
||
|
||
class LoginScreen(QWidget):
|
||
def __init__(self, stacked):
|
||
super().__init__()
|
||
self.stacked = stacked
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
self.showMaximized()
|
||
|
||
# Light blue background
|
||
palette = self.palette()
|
||
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
|
||
self.setPalette(palette)
|
||
self.setAutoFillBackground(True)
|
||
|
||
layout = QVBoxLayout()
|
||
layout.setContentsMargins(40, 30, 40, 30)
|
||
layout.setSpacing(20)
|
||
layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
|
||
|
||
# Logo
|
||
logo = QLabel()
|
||
logo.setPixmap(QPixmap(
|
||
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
|
||
).scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio))
|
||
logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
layout.addWidget(logo)
|
||
|
||
# Title
|
||
title_label = QLabel("CRI DIGITAL MANUFACTURING \n SOLUTIONS")
|
||
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
title_label.setStyleSheet("""
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #002244;
|
||
margin-bottom: 10px;
|
||
""")
|
||
layout.addWidget(title_label)
|
||
|
||
# Username QLineEdit
|
||
self.username = QLineEdit()
|
||
self.username.setPlaceholderText("Username (max 25 chars)")
|
||
self.username.setMaxLength(25)
|
||
self.username.setStyleSheet(self.input_style())
|
||
layout.addWidget(self.username)
|
||
|
||
# Password QLineEdit
|
||
self.password = QLineEdit()
|
||
self.password.setPlaceholderText("Password (max 25 chars)")
|
||
self.password.setMaxLength(25)
|
||
self.password.setEchoMode(QLineEdit.EchoMode.Password)
|
||
self.password.setStyleSheet(self.input_style())
|
||
|
||
# Create eye toggle button with emoji inside password field
|
||
self.eye_button = QToolButton(self.password)
|
||
self.eye_button.setText("🙈") # Initially hidden password
|
||
self.eye_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||
self.eye_button.setStyleSheet("border: none; background: transparent; font-size: 18px;")
|
||
self.eye_button.setToolTip("Show/Hide password")
|
||
self.eye_button.clicked.connect(self.toggle_password_visibility)
|
||
|
||
# Adjust size and margins in QLineEdit for the eye button
|
||
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
|
||
button_size = 24
|
||
self.eye_button.setFixedSize(button_size, button_size)
|
||
self.password.setTextMargins(0, 0, button_size + frame_width, 0)
|
||
|
||
# Position the eye button correctly
|
||
self.position_eye_button()
|
||
|
||
# Reposition on resize or text change
|
||
self.password.textChanged.connect(self.position_eye_button)
|
||
self.password.installEventFilter(self)
|
||
|
||
layout.addWidget(self.password)
|
||
|
||
# Connect Enter key navigation
|
||
self.username.returnPressed.connect(self.password.setFocus)
|
||
self.password.returnPressed.connect(self.do_login)
|
||
|
||
# Login button smaller style
|
||
login_btn = QPushButton("Login")
|
||
login_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background-color: #E91E63;
|
||
color: white;
|
||
font-weight: bold;
|
||
padding: 6px 15px;
|
||
border-radius: 12px;
|
||
font-size: 14px;
|
||
max-width: 120px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #C2185B;
|
||
}
|
||
QPushButton:pressed {
|
||
background-color: #AD1457;
|
||
}
|
||
""")
|
||
login_btn.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||
login_btn.clicked.connect(self.do_login)
|
||
layout.addWidget(login_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
||
|
||
# Secondary image/banner
|
||
logo2 = QLabel()
|
||
logo2.setPixmap(QPixmap(
|
||
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
|
||
).scaled(350, 350, Qt.AspectRatioMode.KeepAspectRatio))
|
||
logo2.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
layout.addWidget(logo2)
|
||
|
||
self.setLayout(layout)
|
||
|
||
def position_eye_button(self):
|
||
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
|
||
button_size = self.eye_button.width()
|
||
self.eye_button.move(
|
||
self.password.rect().right() - button_size - frame_width,
|
||
(self.password.rect().bottom() - button_size + 1) // 2
|
||
)
|
||
|
||
def eventFilter(self, obj, event):
|
||
if obj == self.password and event.type() == QEvent.Type.Resize:
|
||
self.position_eye_button()
|
||
return super().eventFilter(obj, event)
|
||
|
||
def toggle_password_visibility(self):
|
||
if self.password.echoMode() == QLineEdit.EchoMode.Password:
|
||
self.password.setEchoMode(QLineEdit.EchoMode.Normal)
|
||
self.eye_button.setText("👁️")
|
||
else:
|
||
self.password.setEchoMode(QLineEdit.EchoMode.Password)
|
||
self.eye_button.setText("🙈")
|
||
|
||
def input_style(self):
|
||
return """
|
||
QLineEdit {
|
||
background-color: white;
|
||
border: 2px solid #ccc;
|
||
border-radius: 15px;
|
||
padding: 8px 12px;
|
||
color: black;
|
||
font-size: 14px;
|
||
min-width: 300px;
|
||
}
|
||
QLineEdit:focus {
|
||
border: 2px solid purple;
|
||
}
|
||
"""
|
||
|
||
def do_login(self):
|
||
username = self.username.text().strip()
|
||
password = self.password.text().strip()
|
||
|
||
if not username or not password:
|
||
QMessageBox.critical(self, "Input Error", "Please enter both username and password.")
|
||
return
|
||
|
||
url = "https://pds.iotsignin.com/api/testing/user/get-data"
|
||
headers = {
|
||
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
|
||
"User-Name": username,
|
||
"User-Pass": password
|
||
}
|
||
|
||
try:
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
response.raise_for_status()
|
||
data = response.json()
|
||
|
||
if "roles" in data:
|
||
roles = data["roles"]
|
||
roles_str = ', '.join(roles)
|
||
info_msg = f"Login successful!"
|
||
result = QMessageBox.information(self, "Success", info_msg, QMessageBox.StandardButton.Ok)
|
||
if result == QMessageBox.StandardButton.Ok:
|
||
selector_screen = SelectorScreen(self.stacked, username)
|
||
self.stacked.addWidget(selector_screen)
|
||
self.stacked.setCurrentWidget(selector_screen)
|
||
|
||
elif data.get("status_code") == "ERROR":
|
||
description = data.get("status_description", "Unknown error from server.")
|
||
QMessageBox.critical(self, "Login Error", description)
|
||
else:
|
||
QMessageBox.critical(self, "Login Error", "Unexpected response from server.")
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
QMessageBox.critical(self, "Network Error", f"Failed to connect to server:\n{str(e)}")
|
||
|
||
|
||
def main():
|
||
app = QApplication(sys.argv)
|
||
stacked = QStackedWidget()
|
||
|
||
login_screen = LoginScreen(stacked)
|
||
stacked.addWidget(login_screen)
|
||
|
||
stacked.setFixedSize(1200, 800)
|
||
stacked.show()
|
||
|
||
sys.exit(app.exec())
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |