mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-25 18:05:26 +00:00
Add: Dashboard on GUI
This commit is contained in:
parent
9e2445d5b1
commit
a087189e54
@ -60,3 +60,10 @@
|
|||||||
#pywebio-scope-log {
|
#pywebio-scope-log {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[id^="pywebio-scope-dashboard-row-"] {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 4rem;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pywebio-scope-scheduler-bar,
|
|
||||||
#pywebio-scope-log-bar,
|
#pywebio-scope-log-bar,
|
||||||
#pywebio-scope-log,
|
#pywebio-scope-log,
|
||||||
#pywebio-scope-daemon-overview #pywebio-scope-groups {
|
#pywebio-scope-daemon-overview #pywebio-scope-groups {
|
||||||
|
@ -383,17 +383,65 @@ pre.rich-traceback-code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#pywebio-scope-scheduler-bar,
|
#pywebio-scope-scheduler-bar,
|
||||||
#pywebio-scope-log-bar {
|
#pywebio-scope-log-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pywebio-scope-log-bar-btns {
|
#pywebio-scope-log-title-btns {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pywebio-scope-log-bar {
|
||||||
|
height: 11.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pywebio-scope-dashboard {
|
||||||
|
display: flex;
|
||||||
|
align-content: flex-start;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-icon {
|
||||||
|
margin: .6rem .8rem 0 .6rem;
|
||||||
|
width: .5rem;
|
||||||
|
height: .5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="pywebio-scope-dashboard-row-"] {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 4rem;
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[style*="--dashboard-value--"] {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[style*="--dashboard-time--"] {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 400;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="pywebio-scope-dashboard-row-"] p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="pywebio-scope-dashboard-value-"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pywebio-scope-log {
|
#pywebio-scope-log {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
@ -153,3 +153,7 @@ pre.rich-traceback-code {
|
|||||||
[id^="pywebio-scope-group_"] > p + p {
|
[id^="pywebio-scope-group_"] > p + p {
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*[style*="--dashboard-time--"] {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
@ -152,3 +152,7 @@ pre.rich-traceback-code {
|
|||||||
[id^="pywebio-scope-group_"] > p + p {
|
[id^="pywebio-scope-group_"] > p + p {
|
||||||
color: #777777;
|
color: #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*[style*="--dashboard-time--"] {
|
||||||
|
color: #777777;
|
||||||
|
}
|
@ -364,7 +364,9 @@
|
|||||||
"type": "stored",
|
"type": "stored",
|
||||||
"value": {},
|
"value": {},
|
||||||
"display": "hide",
|
"display": "hide",
|
||||||
"stored": "StoredTrailblazePower"
|
"stored": "StoredTrailblazePower",
|
||||||
|
"order": 1,
|
||||||
|
"color": "#eb8efe"
|
||||||
},
|
},
|
||||||
"DungeonDouble": {
|
"DungeonDouble": {
|
||||||
"type": "stored",
|
"type": "stored",
|
||||||
@ -782,7 +784,9 @@
|
|||||||
"type": "stored",
|
"type": "stored",
|
||||||
"value": {},
|
"value": {},
|
||||||
"display": "hide",
|
"display": "hide",
|
||||||
"stored": "StoredDailyActivity"
|
"stored": "StoredDailyActivity",
|
||||||
|
"order": 2,
|
||||||
|
"color": "#ffcf70"
|
||||||
},
|
},
|
||||||
"DailyQuest": {
|
"DailyQuest": {
|
||||||
"type": "stored",
|
"type": "stored",
|
||||||
|
@ -111,6 +111,8 @@ DungeonSupport:
|
|||||||
DungeonStorage:
|
DungeonStorage:
|
||||||
TrailblazePower:
|
TrailblazePower:
|
||||||
stored: StoredTrailblazePower
|
stored: StoredTrailblazePower
|
||||||
|
order: 1
|
||||||
|
color: "#eb8efe"
|
||||||
DungeonDouble:
|
DungeonDouble:
|
||||||
stored: StoredDungeonDouble
|
stored: StoredDungeonDouble
|
||||||
|
|
||||||
@ -124,6 +126,8 @@ AchievableQuest:
|
|||||||
DailyStorage:
|
DailyStorage:
|
||||||
DailyActivity:
|
DailyActivity:
|
||||||
stored: StoredDailyActivity
|
stored: StoredDailyActivity
|
||||||
|
order: 2
|
||||||
|
color: "#ffcf70"
|
||||||
DailyQuest:
|
DailyQuest:
|
||||||
stored: StoredDaily
|
stored: StoredDaily
|
||||||
|
|
||||||
|
@ -52,6 +52,16 @@ Overview:
|
|||||||
Waiting:
|
Waiting:
|
||||||
NoTask:
|
NoTask:
|
||||||
|
|
||||||
|
Dashboard:
|
||||||
|
# From lang.readable_time()
|
||||||
|
NoData:
|
||||||
|
TimeError:
|
||||||
|
JustNow:
|
||||||
|
MinutesAgo:
|
||||||
|
HoursAgo:
|
||||||
|
DaysAgo:
|
||||||
|
LongTimeAgo:
|
||||||
|
|
||||||
AddAlas:
|
AddAlas:
|
||||||
PopupTitle:
|
PopupTitle:
|
||||||
NewName:
|
NewName:
|
||||||
|
84
module/config/argument/stored.json
Normal file
84
module/config/argument/stored.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"TrailblazePower": {
|
||||||
|
"name": "TrailblazePower",
|
||||||
|
"path": "Dungeon.DungeonStorage.TrailblazePower",
|
||||||
|
"i18n": "DungeonStorage.TrailblazePower.name",
|
||||||
|
"stored": "StoredTrailblazePower",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"total": 180,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"order": 1,
|
||||||
|
"color": "#eb8efe"
|
||||||
|
},
|
||||||
|
"DailyActivity": {
|
||||||
|
"name": "DailyActivity",
|
||||||
|
"path": "DailyQuest.DailyStorage.DailyActivity",
|
||||||
|
"i18n": "DailyStorage.DailyActivity.name",
|
||||||
|
"stored": "StoredDailyActivity",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"total": 500,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"order": 2,
|
||||||
|
"color": "#ffcf70"
|
||||||
|
},
|
||||||
|
"Assignment": {
|
||||||
|
"name": "Assignment",
|
||||||
|
"path": "Assignment.Assignment.Assignment",
|
||||||
|
"i18n": "Assignment.Assignment.name",
|
||||||
|
"stored": "StoredAssignment",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"total": 0,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"order": 3,
|
||||||
|
"color": "#deba95"
|
||||||
|
},
|
||||||
|
"SimulatedUniverse": {
|
||||||
|
"name": "SimulatedUniverse",
|
||||||
|
"path": "Dungeon.DungeonStorage.SimulatedUniverse",
|
||||||
|
"i18n": "DungeonStorage.SimulatedUniverse.name",
|
||||||
|
"stored": "StoredSimulatedUniverse",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"total": 0,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"order": 5,
|
||||||
|
"color": "#8fb5fe"
|
||||||
|
},
|
||||||
|
"DungeonDouble": {
|
||||||
|
"name": "DungeonDouble",
|
||||||
|
"path": "Dungeon.DungeonStorage.DungeonDouble",
|
||||||
|
"i18n": "DungeonStorage.DungeonDouble.name",
|
||||||
|
"stored": "StoredDungeonDouble",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"calyx": 0,
|
||||||
|
"relic": 0
|
||||||
|
},
|
||||||
|
"order": 0,
|
||||||
|
"color": "#777777"
|
||||||
|
},
|
||||||
|
"DailyQuest": {
|
||||||
|
"name": "DailyQuest",
|
||||||
|
"path": "DailyQuest.DailyStorage.DailyQuest",
|
||||||
|
"i18n": "DailyStorage.DailyQuest.name",
|
||||||
|
"stored": "StoredDaily",
|
||||||
|
"attrs": {
|
||||||
|
"time": "2020-01-01 00:00:00",
|
||||||
|
"quest1": "",
|
||||||
|
"quest2": "",
|
||||||
|
"quest3": "",
|
||||||
|
"quest4": "",
|
||||||
|
"quest5": "",
|
||||||
|
"quest6": ""
|
||||||
|
},
|
||||||
|
"order": 0,
|
||||||
|
"color": "#777777"
|
||||||
|
}
|
||||||
|
}
|
@ -441,6 +441,32 @@ class ConfigGenerator:
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def stored(self):
|
||||||
|
import module.config.stored.classes as classes
|
||||||
|
data = {}
|
||||||
|
for path, value in deep_iter(self.args, depth=3):
|
||||||
|
if value.get('type') != 'stored':
|
||||||
|
continue
|
||||||
|
name = path[-1]
|
||||||
|
stored = value.get('stored')
|
||||||
|
stored_class = getattr(classes, stored)
|
||||||
|
row = {
|
||||||
|
'name': name,
|
||||||
|
'path': '.'.join(path),
|
||||||
|
'i18n': f'{path[1]}.{path[2]}.name',
|
||||||
|
'stored': stored,
|
||||||
|
'attrs': stored_class('')._attrs,
|
||||||
|
'order': value.get('order', 0),
|
||||||
|
'color': value.get('color', '#777777')
|
||||||
|
}
|
||||||
|
data[name] = row
|
||||||
|
|
||||||
|
# sort by `order` ascending, but `order`==0 at last
|
||||||
|
data = sorted(data.items(), key=lambda kv: (kv[1]['order'] == 0, kv[1]['order']))
|
||||||
|
data = {k: v for k, v in data}
|
||||||
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_deploy_template():
|
def generate_deploy_template():
|
||||||
template = poor_yaml_read(DEPLOY_TEMPLATE)
|
template = poor_yaml_read(DEPLOY_TEMPLATE)
|
||||||
@ -495,12 +521,14 @@ class ConfigGenerator:
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
_ = self.args
|
_ = self.args
|
||||||
_ = self.menu
|
_ = self.menu
|
||||||
|
_ = self.stored
|
||||||
# _ = self.event
|
# _ = self.event
|
||||||
self.insert_assignment()
|
self.insert_assignment()
|
||||||
self.insert_package()
|
self.insert_package()
|
||||||
# self.insert_server()
|
# self.insert_server()
|
||||||
write_file(filepath_args(), self.args)
|
write_file(filepath_args(), self.args)
|
||||||
write_file(filepath_args('menu'), self.menu)
|
write_file(filepath_args('menu'), self.menu)
|
||||||
|
write_file(filepath_args('stored'), self.stored)
|
||||||
self.generate_code()
|
self.generate_code()
|
||||||
self.generate_stored()
|
self.generate_stored()
|
||||||
for lang in LANGUAGES:
|
for lang in LANGUAGES:
|
||||||
|
@ -695,6 +695,15 @@
|
|||||||
"Waiting": "Waiting",
|
"Waiting": "Waiting",
|
||||||
"NoTask": "No Task"
|
"NoTask": "No Task"
|
||||||
},
|
},
|
||||||
|
"Dashboard": {
|
||||||
|
"NoData": "no data",
|
||||||
|
"TimeError": "time error",
|
||||||
|
"JustNow": "just now",
|
||||||
|
"MinutesAgo": "{time}min ago",
|
||||||
|
"HoursAgo": "{time}h ago",
|
||||||
|
"DaysAgo": "{time}d ago",
|
||||||
|
"LongTimeAgo": "long time ago"
|
||||||
|
},
|
||||||
"AddAlas": {
|
"AddAlas": {
|
||||||
"PopupTitle": "Add new config",
|
"PopupTitle": "Add new config",
|
||||||
"NewName": "New name",
|
"NewName": "New name",
|
||||||
|
@ -695,6 +695,15 @@
|
|||||||
"Waiting": "Waiting",
|
"Waiting": "Waiting",
|
||||||
"NoTask": "No Task"
|
"NoTask": "No Task"
|
||||||
},
|
},
|
||||||
|
"Dashboard": {
|
||||||
|
"NoData": "Gui.Dashboard.NoData",
|
||||||
|
"TimeError": "Gui.Dashboard.TimeError",
|
||||||
|
"JustNow": "Gui.Dashboard.JustNow",
|
||||||
|
"MinutesAgo": "Gui.Dashboard.MinutesAgo",
|
||||||
|
"HoursAgo": "Gui.Dashboard.HoursAgo",
|
||||||
|
"DaysAgo": "Gui.Dashboard.DaysAgo",
|
||||||
|
"LongTimeAgo": "Gui.Dashboard.LongTimeAgo"
|
||||||
|
},
|
||||||
"AddAlas": {
|
"AddAlas": {
|
||||||
"PopupTitle": "新しいコンフィグを追加",
|
"PopupTitle": "新しいコンフィグを追加",
|
||||||
"NewName": "コンフィグ名",
|
"NewName": "コンフィグ名",
|
||||||
|
@ -695,6 +695,15 @@
|
|||||||
"Waiting": "等待中",
|
"Waiting": "等待中",
|
||||||
"NoTask": "无任务"
|
"NoTask": "无任务"
|
||||||
},
|
},
|
||||||
|
"Dashboard": {
|
||||||
|
"NoData": "无数据",
|
||||||
|
"TimeError": "时间错误",
|
||||||
|
"JustNow": "刚刚",
|
||||||
|
"MinutesAgo": "{time}分钟前",
|
||||||
|
"HoursAgo": "{time}小时前",
|
||||||
|
"DaysAgo": "{time}天前",
|
||||||
|
"LongTimeAgo": "很久以前"
|
||||||
|
},
|
||||||
"AddAlas": {
|
"AddAlas": {
|
||||||
"PopupTitle": "添加新配置",
|
"PopupTitle": "添加新配置",
|
||||||
"NewName": "新的配置文件名",
|
"NewName": "新的配置文件名",
|
||||||
|
@ -695,6 +695,15 @@
|
|||||||
"Waiting": "等待中",
|
"Waiting": "等待中",
|
||||||
"NoTask": "無任務"
|
"NoTask": "無任務"
|
||||||
},
|
},
|
||||||
|
"Dashboard": {
|
||||||
|
"NoData": "無數據",
|
||||||
|
"TimeError": "時間錯誤",
|
||||||
|
"JustNow": "剛剛",
|
||||||
|
"MinutesAgo": "{time}分鐘前",
|
||||||
|
"HoursAgo": "{time}小時前",
|
||||||
|
"DaysAgo": "{time}天前",
|
||||||
|
"LongTimeAgo": "很久以前"
|
||||||
|
},
|
||||||
"AddAlas": {
|
"AddAlas": {
|
||||||
"PopupTitle": "添加新的設定",
|
"PopupTitle": "添加新的設定",
|
||||||
"NewName": "新設定的檔案名",
|
"NewName": "新設定的檔案名",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import cached_property as functools_cached_property
|
from functools import cached_property as functools_cached_property
|
||||||
|
|
||||||
@ -111,29 +110,6 @@ class StoredBase:
|
|||||||
from module.logger import logger
|
from module.logger import logger
|
||||||
logger.attr(self._name, self._stored)
|
logger.attr(self._name, self._stored)
|
||||||
|
|
||||||
def dashboard(self) -> str:
|
|
||||||
"""
|
|
||||||
Return a string to show on GUI
|
|
||||||
"""
|
|
||||||
return 'None'
|
|
||||||
|
|
||||||
def readable_time(self):
|
|
||||||
diff = self.time.timestamp() - time.time()
|
|
||||||
if diff < -1:
|
|
||||||
return '', 'TimeError'
|
|
||||||
elif diff < 60:
|
|
||||||
# < 1 min
|
|
||||||
return '', 'JustNow'
|
|
||||||
elif diff < 3600:
|
|
||||||
return str(int(diff // 60)), 'MinutesAgo'
|
|
||||||
elif diff < 86400:
|
|
||||||
return str(int(diff // 86400)), 'HoursAgo'
|
|
||||||
elif diff < 129600:
|
|
||||||
return str(int(diff // 129600)), 'DaysAgo'
|
|
||||||
else:
|
|
||||||
# > 15 days
|
|
||||||
return '', 'LongTimeAgo'
|
|
||||||
|
|
||||||
|
|
||||||
class StoredExpiredAt0400(StoredBase):
|
class StoredExpiredAt0400(StoredBase):
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
@ -154,11 +130,11 @@ class StoredCounter(StoredBase):
|
|||||||
|
|
||||||
FIXED_TOTAL = 0
|
FIXED_TOTAL = 0
|
||||||
|
|
||||||
def set(self, current, total=0):
|
def set(self, value, total=0):
|
||||||
if self.FIXED_TOTAL:
|
if self.FIXED_TOTAL:
|
||||||
total = self.FIXED_TOTAL
|
total = self.FIXED_TOTAL
|
||||||
with self._config.multi_set():
|
with self._config.multi_set():
|
||||||
self.value = current
|
self.value = value
|
||||||
self.total = total
|
self.total = total
|
||||||
|
|
||||||
def to_counter(self) -> str:
|
def to_counter(self) -> str:
|
||||||
@ -170,6 +146,13 @@ class StoredCounter(StoredBase):
|
|||||||
def get_remain(self) -> int:
|
def get_remain(self) -> int:
|
||||||
return self.total - self.value
|
return self.total - self.value
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def _attrs(self) -> dict:
|
||||||
|
attrs = super()._attrs
|
||||||
|
if self.FIXED_TOTAL:
|
||||||
|
attrs['total'] = self.FIXED_TOTAL
|
||||||
|
return attrs
|
||||||
|
|
||||||
@functools_cached_property
|
@functools_cached_property
|
||||||
def _stored(self):
|
def _stored(self):
|
||||||
stored = super()._stored
|
stored = super()._stored
|
||||||
|
@ -88,11 +88,13 @@ task_handler = TaskHandler()
|
|||||||
class AlasGUI(Frame):
|
class AlasGUI(Frame):
|
||||||
ALAS_MENU: Dict[str, Dict[str, List[str]]]
|
ALAS_MENU: Dict[str, Dict[str, List[str]]]
|
||||||
ALAS_ARGS: Dict[str, Dict[str, Dict[str, Dict[str, str]]]]
|
ALAS_ARGS: Dict[str, Dict[str, Dict[str, Dict[str, str]]]]
|
||||||
|
ALAS_STORED: Dict[str, Dict[str, Dict[str, str]]]
|
||||||
theme = "default"
|
theme = "default"
|
||||||
|
|
||||||
def initial(self) -> None:
|
def initial(self) -> None:
|
||||||
self.ALAS_MENU = read_file(filepath_args("menu", self.alas_mod))
|
self.ALAS_MENU = read_file(filepath_args("menu", self.alas_mod))
|
||||||
self.ALAS_ARGS = read_file(filepath_args("args", self.alas_mod))
|
self.ALAS_ARGS = read_file(filepath_args("args", self.alas_mod))
|
||||||
|
self.ALAS_STORED = read_file(filepath_args("stored", self.alas_mod))
|
||||||
self._init_alas_config_watcher()
|
self._init_alas_config_watcher()
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -318,6 +320,35 @@ class AlasGUI(Frame):
|
|||||||
color="navigator",
|
color="navigator",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_dashboard(self, arg, arg_dict, config):
|
||||||
|
i18n = arg_dict.get('i18n')
|
||||||
|
if i18n:
|
||||||
|
name = t(i18n)
|
||||||
|
else:
|
||||||
|
name = arg
|
||||||
|
color = arg_dict.get("color", "#777777")
|
||||||
|
nodata = t("Gui.Dashboard.NoData")
|
||||||
|
|
||||||
|
def set_value(dic):
|
||||||
|
if "total" in dic.get("attrs", []) and config.get("total") is not None:
|
||||||
|
return [
|
||||||
|
put_text(config.get("value", nodata)).style("--dashboard-value--"),
|
||||||
|
put_text(f' / {config.get("total", "")}').style("--dashboard-time--"),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return [
|
||||||
|
put_text(config.get("value", nodata)).style("--dashboard-value--"),
|
||||||
|
]
|
||||||
|
|
||||||
|
with use_scope(f"dashboard-row-{arg}", clear=True):
|
||||||
|
put_html(f'<div><div class="dashboard-icon" style="background-color:{color}"></div>'),
|
||||||
|
put_scope(f"dashboard-content-{arg}", [
|
||||||
|
put_scope(f"dashboard-value-{arg}", set_value(arg_dict)),
|
||||||
|
put_scope(f"dashboard-time-{arg}", [
|
||||||
|
put_text(f"{name} - {lang.readable_time(config.get('time', ''))}").style("--dashboard-time--"),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
@use_scope("content", clear=True)
|
@use_scope("content", clear=True)
|
||||||
def alas_overview(self) -> None:
|
def alas_overview(self) -> None:
|
||||||
self.init_menu(name="Overview")
|
self.init_menu(name="Overview")
|
||||||
@ -374,20 +405,19 @@ class AlasGUI(Frame):
|
|||||||
log = RichLog("log")
|
log = RichLog("log")
|
||||||
|
|
||||||
with use_scope("logs"):
|
with use_scope("logs"):
|
||||||
put_scope(
|
put_scope("log-bar", [
|
||||||
"log-bar",
|
put_scope("log-title", [
|
||||||
[
|
put_text(t("Gui.Overview.Log")).style("font-size: 1.25rem; margin: auto .5rem auto;"),
|
||||||
put_text(t("Gui.Overview.Log")).style(
|
put_scope("log-title-btns", [
|
||||||
"font-size: 1.25rem; margin: auto .5rem auto;"
|
|
||||||
),
|
|
||||||
put_scope(
|
|
||||||
"log-bar-btns",
|
|
||||||
[
|
|
||||||
put_scope("log_scroll_btn"),
|
put_scope("log_scroll_btn"),
|
||||||
],
|
]),
|
||||||
),
|
]),
|
||||||
],
|
put_html('<hr class="hr-group">'),
|
||||||
),
|
put_scope("dashboard", [
|
||||||
|
# Empty dashboard, values will be updated in alas_update_overview_task()
|
||||||
|
put_scope(f"dashboard-row-{arg}", []) for arg in self.ALAS_STORED.keys()
|
||||||
|
])
|
||||||
|
])
|
||||||
put_scope("log", [put_html("")])
|
put_scope("log", [put_html("")])
|
||||||
|
|
||||||
log.console.width = log.get_width()
|
log.console.width = log.get_width()
|
||||||
@ -501,6 +531,7 @@ class AlasGUI(Frame):
|
|||||||
self.alas_config.load()
|
self.alas_config.load()
|
||||||
self.alas_config.get_next_task()
|
self.alas_config.get_next_task()
|
||||||
|
|
||||||
|
alive = self.alas.alive
|
||||||
if len(self.alas_config.pending_task) >= 1:
|
if len(self.alas_config.pending_task) >= 1:
|
||||||
if self.alas.alive:
|
if self.alas.alive:
|
||||||
running = self.alas_config.pending_task[:1]
|
running = self.alas_config.pending_task[:1]
|
||||||
@ -528,6 +559,8 @@ class AlasGUI(Frame):
|
|||||||
color="off",
|
color="off",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.scope_expired("scheduler_alive", alive) \
|
||||||
|
or self.scope_expired("pending_task", self.alas_config.pending_task):
|
||||||
clear("running_tasks")
|
clear("running_tasks")
|
||||||
clear("pending_tasks")
|
clear("pending_tasks")
|
||||||
clear("waiting_tasks")
|
clear("waiting_tasks")
|
||||||
@ -549,6 +582,16 @@ class AlasGUI(Frame):
|
|||||||
put_task(task)
|
put_task(task)
|
||||||
else:
|
else:
|
||||||
put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--")
|
put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--")
|
||||||
|
self.scope_add("scheduler_alive", alive)
|
||||||
|
self.scope_add("pending_task", self.alas_config.pending_task)
|
||||||
|
|
||||||
|
for arg, arg_dict in self.ALAS_STORED.items():
|
||||||
|
path = arg_dict["path"]
|
||||||
|
if self.scope_expired_then_add(
|
||||||
|
key=f"dashboard-time-value-{arg}",
|
||||||
|
value=lang.readable_time(deep_get(self.alas_config.data, keys=f"{path}.time"))
|
||||||
|
):
|
||||||
|
self.set_dashboard(arg, arg_dict, deep_get(self.alas_config.data, keys=path, default={}))
|
||||||
|
|
||||||
@use_scope("content", clear=True)
|
@use_scope("content", clear=True)
|
||||||
def alas_daemon_overview(self, task: str) -> None:
|
def alas_daemon_overview(self, task: str) -> None:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
from pywebio.output import clear, put_html, put_scope, put_text, use_scope
|
from pywebio.output import clear, put_html, put_scope, put_text, use_scope
|
||||||
from pywebio.session import defer_call, info, run_js
|
from pywebio.session import defer_call, info, run_js
|
||||||
|
|
||||||
@ -13,12 +15,34 @@ class Base:
|
|||||||
self.is_mobile = info.user_agent.is_mobile
|
self.is_mobile = info.user_agent.is_mobile
|
||||||
# Task handler
|
# Task handler
|
||||||
self.task_handler = WebIOTaskHandler()
|
self.task_handler = WebIOTaskHandler()
|
||||||
|
# Record scopes to reduce data transfer to frontend
|
||||||
|
# Key: scope name, value: last update time
|
||||||
|
self.scope: Dict[str, Any] = {}
|
||||||
defer_call(self.stop)
|
defer_call(self.stop)
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
self.alive = False
|
self.alive = False
|
||||||
self.task_handler.stop()
|
self.task_handler.stop()
|
||||||
|
|
||||||
|
def scope_clear(self):
|
||||||
|
self.scope = {}
|
||||||
|
|
||||||
|
def scope_add(self, key, value):
|
||||||
|
self.scope[key] = value
|
||||||
|
|
||||||
|
def scope_expired(self, key, value) -> bool:
|
||||||
|
try:
|
||||||
|
return self.scope[key] != value
|
||||||
|
except KeyError:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def scope_expired_then_add(self, key, value) -> bool:
|
||||||
|
if self.scope_expired(key, value):
|
||||||
|
self.scope_add(key, value)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Frame(Base):
|
class Frame(Base):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -33,6 +57,7 @@ class Frame(Base):
|
|||||||
name: button name(label) to be highlight
|
name: button name(label) to be highlight
|
||||||
"""
|
"""
|
||||||
self.visible = True
|
self.visible = True
|
||||||
|
self.scope_clear()
|
||||||
self.task_handler.remove_pending_task()
|
self.task_handler.remove_pending_task()
|
||||||
clear("menu")
|
clear("menu")
|
||||||
if expand_menu:
|
if expand_menu:
|
||||||
@ -50,6 +75,7 @@ class Frame(Base):
|
|||||||
"""
|
"""
|
||||||
self.visible = True
|
self.visible = True
|
||||||
self.page = name
|
self.page = name
|
||||||
|
self.scope_clear()
|
||||||
self.task_handler.remove_pending_task()
|
self.task_handler.remove_pending_task()
|
||||||
clear("content")
|
clear("content")
|
||||||
if collapse_menu:
|
if collapse_menu:
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import time
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from module.config.utils import *
|
from module.config.utils import *
|
||||||
from module.webui.setting import State
|
|
||||||
from module.webui.fake import list_mod
|
from module.webui.fake import list_mod
|
||||||
|
from module.webui.setting import State
|
||||||
|
|
||||||
LANG = "zh-CN"
|
LANG = "zh-CN"
|
||||||
TRANSLATE_MODE = False
|
TRANSLATE_MODE = False
|
||||||
@ -67,3 +68,33 @@ def reload():
|
|||||||
for key in dic_lang["ja-JP"].keys():
|
for key in dic_lang["ja-JP"].keys():
|
||||||
if dic_lang["ja-JP"][key] == key:
|
if dic_lang["ja-JP"][key] == key:
|
||||||
dic_lang["ja-JP"][key] = dic_lang["en-US"][key]
|
dic_lang["ja-JP"][key] = dic_lang["en-US"][key]
|
||||||
|
|
||||||
|
|
||||||
|
def readable_time(before: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert "2023-08-29 12:30:53" to "3 Minutes Ago"
|
||||||
|
"""
|
||||||
|
if not before:
|
||||||
|
return t("Gui.Dashboard.NoData")
|
||||||
|
try:
|
||||||
|
ti = datetime.fromisoformat(before)
|
||||||
|
except ValueError:
|
||||||
|
return t("Gui.Dashboard.TimeError")
|
||||||
|
if ti == DEFAULT_TIME:
|
||||||
|
return t("Gui.Dashboard.NoData")
|
||||||
|
|
||||||
|
diff = time.time() - ti.timestamp()
|
||||||
|
if diff < -1:
|
||||||
|
return t("Gui.Dashboard.TimeError")
|
||||||
|
elif diff < 60:
|
||||||
|
# < 1 min
|
||||||
|
return t("Gui.Dashboard.JustNow")
|
||||||
|
elif diff < 3600:
|
||||||
|
return t("Gui.Dashboard.MinutesAgo", time=int(diff // 60))
|
||||||
|
elif diff < 86400:
|
||||||
|
return t("Gui.Dashboard.HoursAgo", time=int(diff // 3600))
|
||||||
|
elif diff < 129600:
|
||||||
|
return t("Gui.Dashboard.DaysAgo", time=int(diff // 86400))
|
||||||
|
else:
|
||||||
|
# > 15 days
|
||||||
|
return t("Gui.Dashboard.LongTimeAgo")
|
||||||
|
Loading…
Reference in New Issue
Block a user