Compare commits
No commits in common. "768544511ceb28a153bbc5401e110a6893b9c58f" and "ab79bea576ae7a332a22b863060312ddb286e833" have entirely different histories.
768544511c
...
ab79bea576
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"models_dir": "${HOME}$/py/sage/models",
|
|
||||||
"output_dir": "${HOME}$/py/sage/wwwroot/_a/hotspot_analysis",
|
|
||||||
"dbname": "sage",
|
|
||||||
"tblname": "hotspot_analysis",
|
|
||||||
"title": "热点分析",
|
|
||||||
"params": {
|
|
||||||
"sortby": "analyzed_at desc",
|
|
||||||
"browserfields": {
|
|
||||||
"exclouded": ["id"],
|
|
||||||
"cwidth": {}
|
|
||||||
},
|
|
||||||
"editexclouded": [
|
|
||||||
"id",
|
|
||||||
"analyzed_at"
|
|
||||||
],
|
|
||||||
"record_toolbar": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"models_dir": "${HOME}$/py/sage/models",
|
|
||||||
"output_dir": "${HOME}$/py/sage/wwwroot/_a/hotspot_item",
|
|
||||||
"dbname": "sage",
|
|
||||||
"tblname": "hotspot_item",
|
|
||||||
"title": "热点条目",
|
|
||||||
"params": {
|
|
||||||
"sortby": "heat_score desc",
|
|
||||||
"browserfields": {
|
|
||||||
"exclouded": ["id", "content"],
|
|
||||||
"cwidth": {
|
|
||||||
"title": 300,
|
|
||||||
"summary": 400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"editexclouded": [
|
|
||||||
"id",
|
|
||||||
"fetch_time"
|
|
||||||
],
|
|
||||||
"record_toolbar": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"models_dir": "${HOME}$/py/sage/models",
|
|
||||||
"output_dir": "${HOME}$/py/sage/wwwroot/_a/hotspot_source",
|
|
||||||
"dbname": "sage",
|
|
||||||
"tblname": "hotspot_source",
|
|
||||||
"title": "热点来源",
|
|
||||||
"params": {
|
|
||||||
"sortby": "name",
|
|
||||||
"browserfields": {
|
|
||||||
"exclouded": ["id"],
|
|
||||||
"cwidth": {}
|
|
||||||
},
|
|
||||||
"editexclouded": [
|
|
||||||
"id",
|
|
||||||
"last_fetch",
|
|
||||||
"created_at"
|
|
||||||
],
|
|
||||||
"record_toolbar": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
load_path.py
23
load_path.py
@ -1485,29 +1485,6 @@ paths="""/ any
|
|||||||
/dashboard_for_sage/table_top_providers_count.ui logined
|
/dashboard_for_sage/table_top_providers_count.ui logined
|
||||||
/dashboard_for_sage/table_top_users_count.ui logined
|
/dashboard_for_sage/table_top_users_count.ui logined
|
||||||
/dashboard_for_sage/chart_top_models.ui logined
|
/dashboard_for_sage/chart_top_models.ui logined
|
||||||
/hotspot logined
|
|
||||||
/hotspot/index.ui logined
|
|
||||||
/hotspot/stats.dspy logined
|
|
||||||
/hotspot/fetch_now.dspy logined
|
|
||||||
/hotspot/analyze.dspy logined
|
|
||||||
/hotspot_source logined
|
|
||||||
/hotspot_source/index.ui logined
|
|
||||||
/hotspot_source/get_hotspot_source.dspy logined
|
|
||||||
/hotspot_source/add_hotspot_source.dspy logined
|
|
||||||
/hotspot_source/update_hotspot_source.dspy logined
|
|
||||||
/hotspot_source/delete_hotspot_source.dspy logined
|
|
||||||
/hotspot_item logined
|
|
||||||
/hotspot_item/index.ui logined
|
|
||||||
/hotspot_item/get_hotspot_item.dspy logined
|
|
||||||
/hotspot_item/add_hotspot_item.dspy logined
|
|
||||||
/hotspot_item/update_hotspot_item.dspy logined
|
|
||||||
/hotspot_item/delete_hotspot_item.dspy logined
|
|
||||||
/hotspot_analysis logined
|
|
||||||
/hotspot_analysis/index.ui logined
|
|
||||||
/hotspot_analysis/get_hotspot_analysis.dspy logined
|
|
||||||
/hotspot_analysis/add_hotspot_analysis.dspy logined
|
|
||||||
/hotspot_analysis/update_hotspot_analysis.dspy logined
|
|
||||||
/hotspot_analysis/delete_hotspot_analysis.dspy logined
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6578,70 +6578,4 @@ default charset=utf8
|
|||||||
comment '模型供应商'
|
comment '模型供应商'
|
||||||
;
|
;
|
||||||
|
|
||||||
-- ./hotspot_source
|
|
||||||
drop table if exists hotspot_source;
|
|
||||||
CREATE TABLE hotspot_source
|
|
||||||
(
|
|
||||||
`id` VARCHAR(32) comment 'id',
|
|
||||||
`name` VARCHAR(200) comment '来源名称',
|
|
||||||
`type` VARCHAR(20) comment '类型: rss/api/web',
|
|
||||||
`url` VARCHAR(2000) comment '来源URL',
|
|
||||||
`fetch_method` VARCHAR(20) DEFAULT 'GET' comment '请求方法',
|
|
||||||
`headers` TEXT comment '自定义请求头 JSON',
|
|
||||||
`enabled` VARCHAR(1) DEFAULT '1' comment '是否启用',
|
|
||||||
`fetch_interval_min` INT DEFAULT 60 comment '抓取间隔(分钟)',
|
|
||||||
`last_fetch` VARCHAR(50) comment '上次抓取时间',
|
|
||||||
`created_at` VARCHAR(50) comment '创建时间'
|
|
||||||
,primary key(id)
|
|
||||||
)
|
|
||||||
engine=innodb
|
|
||||||
default charset=utf8
|
|
||||||
comment '热点来源'
|
|
||||||
;
|
|
||||||
|
|
||||||
-- ./hotspot_item
|
|
||||||
drop table if exists hotspot_item;
|
|
||||||
CREATE TABLE hotspot_item
|
|
||||||
(
|
|
||||||
`id` VARCHAR(32) comment 'id',
|
|
||||||
`source_id` VARCHAR(32) comment '来源ID',
|
|
||||||
`title` VARCHAR(500) comment '标题',
|
|
||||||
`url` VARCHAR(2000) comment '原文链接',
|
|
||||||
`summary` TEXT comment '摘要',
|
|
||||||
`content` TEXT comment '正文',
|
|
||||||
`heat_score` INT DEFAULT 0 comment '热度分数',
|
|
||||||
`publish_time` VARCHAR(50) comment '发布时间',
|
|
||||||
`fetch_time` VARCHAR(50) comment '抓取时间',
|
|
||||||
`status` VARCHAR(20) DEFAULT 'rising' comment '状态: rising/hot/cooling/expired',
|
|
||||||
`category` VARCHAR(100) comment '分类',
|
|
||||||
`tags` VARCHAR(500) comment '标签逗号分隔'
|
|
||||||
,primary key(id)
|
|
||||||
)
|
|
||||||
engine=innodb
|
|
||||||
default charset=utf8
|
|
||||||
comment '热点条目'
|
|
||||||
;
|
|
||||||
|
|
||||||
CREATE INDEX hotspot_item_source_idx ON hotspot_item(source_id);
|
|
||||||
CREATE INDEX hotspot_item_status_idx ON hotspot_item(status);
|
|
||||||
|
|
||||||
-- ./hotspot_analysis
|
|
||||||
drop table if exists hotspot_analysis;
|
|
||||||
CREATE TABLE hotspot_analysis
|
|
||||||
(
|
|
||||||
`id` VARCHAR(32) comment 'id',
|
|
||||||
`item_id` VARCHAR(32) comment '关联热点ID',
|
|
||||||
`dimension` VARCHAR(50) comment '分析维度',
|
|
||||||
`score` DOUBLE(10,2) DEFAULT 0 comment '维度得分',
|
|
||||||
`analysis_data` TEXT comment '分析数据JSON',
|
|
||||||
`analyzed_at` VARCHAR(50) comment '分析时间'
|
|
||||||
,primary key(id)
|
|
||||||
)
|
|
||||||
engine=innodb
|
|
||||||
default charset=utf8
|
|
||||||
comment '热点分析'
|
|
||||||
;
|
|
||||||
|
|
||||||
CREATE INDEX hotspot_analysis_item_idx ON hotspot_analysis(item_id);
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
# 自动分析热点状态:根据时间和热度自动分类
|
|
||||||
# rising -> hot -> cooling -> expired
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from sqlor.dbpools import DBPools
|
|
||||||
|
|
||||||
async def main(request):
|
|
||||||
db = DBPools()
|
|
||||||
now = datetime.now()
|
|
||||||
analyzed = 0
|
|
||||||
|
|
||||||
async with db.sqlorContext('sage') as sor:
|
|
||||||
items = await sor.sqlExe(
|
|
||||||
"select * from hotspot_item order by fetch_time desc",
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
try:
|
|
||||||
fetch_time = datetime.strptime(str(item.get('fetch_time', '')), '%Y-%m-%d %H:%M:%S')
|
|
||||||
except:
|
|
||||||
fetch_time = now
|
|
||||||
|
|
||||||
hours_ago = (now - fetch_time).total_seconds() / 3600
|
|
||||||
heat = int(item.get('heat_score', 0))
|
|
||||||
current_status = item.get('status', 'rising')
|
|
||||||
|
|
||||||
# 热度衰减计算
|
|
||||||
decay_factor = max(0, 1 - hours_ago / 168) # 7天衰减到0
|
|
||||||
adjusted_heat = heat * decay_factor
|
|
||||||
|
|
||||||
# 状态分类
|
|
||||||
if hours_ago > 168: # 超过7天
|
|
||||||
new_status = 'expired'
|
|
||||||
elif hours_ago > 72: # 超过3天
|
|
||||||
new_status = 'cooling'
|
|
||||||
elif adjusted_heat > 500:
|
|
||||||
new_status = 'hot'
|
|
||||||
elif adjusted_heat > 100:
|
|
||||||
new_status = 'rising'
|
|
||||||
else:
|
|
||||||
new_status = 'cooling' if hours_ago > 24 else 'rising'
|
|
||||||
|
|
||||||
if new_status != current_status:
|
|
||||||
async with db.sqlorContext('sage') as sor:
|
|
||||||
await sor.U('hotspot_item', {
|
|
||||||
'id': item['id'],
|
|
||||||
'status': new_status,
|
|
||||||
'heat_score': int(adjusted_heat),
|
|
||||||
})
|
|
||||||
analyzed += 1
|
|
||||||
|
|
||||||
return {
|
|
||||||
'analyzed': analyzed,
|
|
||||||
'time': now.strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
# 从所有启用的来源抓取热点数据
|
|
||||||
import json
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from datetime import datetime
|
|
||||||
from appPublic.uniqueID import getID
|
|
||||||
from sqlor.dbpools import DBPools
|
|
||||||
|
|
||||||
async def fetch_rss(url):
|
|
||||||
"""解析 RSS/Atom feed"""
|
|
||||||
import aiohttp
|
|
||||||
items = []
|
|
||||||
try:
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
return items
|
|
||||||
text = await resp.text()
|
|
||||||
|
|
||||||
root = ET.fromstring(text)
|
|
||||||
ns = {'atom': 'http://www.w3.org/2005/Atom'}
|
|
||||||
|
|
||||||
# RSS 2.0
|
|
||||||
for item in root.findall('.//item'):
|
|
||||||
title = item.findtext('title', '')
|
|
||||||
link = item.findtext('link', '')
|
|
||||||
desc = item.findtext('description', '')[:500]
|
|
||||||
pub_date = item.findtext('pubDate', '')
|
|
||||||
items.append({
|
|
||||||
'title': title,
|
|
||||||
'url': link,
|
|
||||||
'summary': desc,
|
|
||||||
'publish_time': pub_date,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Atom
|
|
||||||
if not items:
|
|
||||||
for entry in root.findall('.//atom:entry', ns):
|
|
||||||
title = entry.findtext('atom:title', '', ns)
|
|
||||||
link_el = entry.find('atom:link', ns)
|
|
||||||
link = link_el.get('href', '') if link_el is not None else ''
|
|
||||||
summary = entry.findtext('atom:summary', '', ns) or ''
|
|
||||||
updated = entry.findtext('atom:updated', '', ns) or ''
|
|
||||||
items.append({
|
|
||||||
'title': title,
|
|
||||||
'url': link,
|
|
||||||
'summary': summary[:500],
|
|
||||||
'publish_time': updated,
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
print(f'RSS fetch error for {url}: {e}')
|
|
||||||
return items
|
|
||||||
|
|
||||||
async def fetch_api(url, method='GET', headers=None, json_path=None):
|
|
||||||
"""调用 API 端点获取热点数据"""
|
|
||||||
import aiohttp
|
|
||||||
items = []
|
|
||||||
try:
|
|
||||||
hdrs = {}
|
|
||||||
if headers:
|
|
||||||
try:
|
|
||||||
hdrs = json.loads(headers)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
if method.upper() == 'GET':
|
|
||||||
async with session.get(url, headers=hdrs, timeout=aiohttp.ClientTimeout(total=30)) as resp:
|
|
||||||
if resp.status == 200:
|
|
||||||
data = await resp.json()
|
|
||||||
else:
|
|
||||||
async with session.post(url, headers=hdrs, timeout=aiohttp.ClientTimeout(total=30)) as resp:
|
|
||||||
if resp.status == 200:
|
|
||||||
data = await resp.json()
|
|
||||||
|
|
||||||
# 尝试从 JSON 中提取 items
|
|
||||||
if isinstance(data, list):
|
|
||||||
entries = data
|
|
||||||
elif isinstance(data, dict):
|
|
||||||
# 常见热点 API 格式
|
|
||||||
entries = data.get('data', data.get('items', data.get('list', [])))
|
|
||||||
else:
|
|
||||||
entries = []
|
|
||||||
|
|
||||||
for entry in entries[:50]: # limit
|
|
||||||
if isinstance(entry, dict):
|
|
||||||
title = entry.get('title', entry.get('name', ''))
|
|
||||||
url_val = entry.get('url', entry.get('link', ''))
|
|
||||||
summary = entry.get('summary', entry.get('description', entry.get('desc', '')))
|
|
||||||
heat = entry.get('heat', entry.get('hot', entry.get('score', 0)))
|
|
||||||
items.append({
|
|
||||||
'title': str(title),
|
|
||||||
'url': str(url_val),
|
|
||||||
'summary': str(summary)[:500],
|
|
||||||
'heat_score': int(heat) if heat else 0,
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
print(f'API fetch error for {url}: {e}')
|
|
||||||
return items
|
|
||||||
|
|
||||||
async def main(request):
|
|
||||||
db = DBPools()
|
|
||||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
new_items = 0
|
|
||||||
total_sources = 0
|
|
||||||
|
|
||||||
async with db.sqlorContext('sage') as sor:
|
|
||||||
sources = await sor.R('hotspot_source', {})
|
|
||||||
|
|
||||||
for src in sources:
|
|
||||||
if src.get('enabled') != '1':
|
|
||||||
continue
|
|
||||||
|
|
||||||
total_sources += 1
|
|
||||||
src_type = src.get('type', 'rss')
|
|
||||||
src_url = src.get('url', '')
|
|
||||||
|
|
||||||
if not src_url:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if src_type == 'rss':
|
|
||||||
items = await fetch_rss(src_url)
|
|
||||||
elif src_type == 'api':
|
|
||||||
fetch_method = src.get('fetch_method', 'GET')
|
|
||||||
headers = src.get('headers', '')
|
|
||||||
items = await fetch_api(src_url, fetch_method, headers)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 存入数据库
|
|
||||||
async with db.sqlorContext('sage') as sor:
|
|
||||||
# 更新 last_fetch
|
|
||||||
await sor.U('hotspot_source', {
|
|
||||||
'id': src['id'],
|
|
||||||
'last_fetch': now,
|
|
||||||
})
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
# 去重:检查 URL 是否已存在
|
|
||||||
existing = await sor.sqlExe(
|
|
||||||
"select id from hotspot_item where url=${url}$",
|
|
||||||
{'url': item.get('url', '')}
|
|
||||||
)
|
|
||||||
if existing:
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_id = getID()
|
|
||||||
status = 'rising' # 新抓取的默认 rising
|
|
||||||
await sor.C('hotspot_item', {
|
|
||||||
'id': new_id,
|
|
||||||
'source_id': src['id'],
|
|
||||||
'title': item.get('title', ''),
|
|
||||||
'url': item.get('url', ''),
|
|
||||||
'summary': item.get('summary', '')[:500],
|
|
||||||
'heat_score': item.get('heat_score', 100),
|
|
||||||
'publish_time': item.get('publish_time', now),
|
|
||||||
'fetch_time': now,
|
|
||||||
'status': status,
|
|
||||||
})
|
|
||||||
new_items += 1
|
|
||||||
|
|
||||||
return {
|
|
||||||
'total': total_sources,
|
|
||||||
'new_items': new_items,
|
|
||||||
'time': now,
|
|
||||||
}
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
{% set roles = get_user_roles(get_user()) %}
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "热点雷达系统",
|
|
||||||
"style": "font-size:24px;font-weight:bold;padding:16px 16px 0 16px;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "指定来源的热点聚合分析平台",
|
|
||||||
"style": "font-size:14px;color:#999;padding:0 16px 16px 16px;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_total",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "热点总数", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#1890ff;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#f0f5ff;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_rising",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "上升中", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#52c41a;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#f6ffed;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_hot",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "正热点", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#ff4d4f;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#fff2f0;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_cooling",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "降温中", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#faad14;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#fffbe6;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_expired",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "已过期", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#999;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#f5f5f5;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"id": "stat_sources",
|
|
||||||
"children": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "数据来源", "style": "color:#666;font-size:12px;"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "0", "style": "font-size:28px;font-weight:bold;color:#722ed1;"}}
|
|
||||||
],
|
|
||||||
"style": "padding:16px;background:#f9f0ff;border-radius:8px;margin:8px;min-width:100px;text-align:center;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"label": "立即抓取全部来源",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "fetch('/hotspot/fetch_now.dspy').then(r=>r.json()).then(d=>{alert('抓取完成: 处理'+d.total+'个来源,新增'+d.new_items+'条');setTimeout(()=>location.reload(),1000);}).catch(e=>alert('抓取出错: '+e))",
|
|
||||||
"style": "margin:8px;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"label": "刷新统计",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "fetch('/hotspot/stats.dspy').then(r=>r.json()).then(d=>{document.querySelectorAll('[id^=stat_]').forEach(el=>{const num=el.querySelectorAll('div')[1];if(num){const k=el.id.replace('stat_','');num.textContent=d[k]||0;}});}).catch(e=>console.error(e))",
|
|
||||||
"style": "margin:8px;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Tab",
|
|
||||||
"options": {
|
|
||||||
"tabs": [
|
|
||||||
{
|
|
||||||
"title": "热点条目",
|
|
||||||
"content": {
|
|
||||||
"widgettype": "urlwidget",
|
|
||||||
"options": {
|
|
||||||
"url": "{{ entire_url('hotspot_item') }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "来源管理",
|
|
||||||
"content": {
|
|
||||||
"widgettype": "urlwidget",
|
|
||||||
"options": {
|
|
||||||
"url": "{{ entire_url('hotspot_source') }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "分析记录",
|
|
||||||
"content": {
|
|
||||||
"widgettype": "urlwidget",
|
|
||||||
"options": {
|
|
||||||
"url": "{{ entire_url('hotspot_analysis') }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "提示:在「来源管理」中添加 RSS/API 来源后,点击「立即抓取」获取热点数据。",
|
|
||||||
"style": "font-size:12px;color:#bbb;padding:16px;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
from appPublic.uniqueID import getID
|
|
||||||
from sqlor.dbpools import DBPools
|
|
||||||
|
|
||||||
async def main(request):
|
|
||||||
db = DBPools()
|
|
||||||
async with db.sqlorContext('sage') as sor:
|
|
||||||
items = await sor.R('hotspot_item', {'order': 'fetch_time desc'})
|
|
||||||
sources = await sor.R('hotspot_source', {})
|
|
||||||
|
|
||||||
total = len(items)
|
|
||||||
rising = sum(1 for i in items if i.get('status') == 'rising')
|
|
||||||
hot = sum(1 for i in items if i.get('status') == 'hot')
|
|
||||||
cooling = sum(1 for i in items if i.get('status') == 'cooling')
|
|
||||||
expired = sum(1 for i in items if i.get('status') == 'expired')
|
|
||||||
sources_count = len([s for s in sources if s.get('enabled') == '1'])
|
|
||||||
|
|
||||||
return {
|
|
||||||
'total': total,
|
|
||||||
'rising': rising,
|
|
||||||
'hot': hot,
|
|
||||||
'cooling': cooling,
|
|
||||||
'expired': expired,
|
|
||||||
'sources': sources_count
|
|
||||||
}
|
|
||||||
@ -15,11 +15,6 @@
|
|||||||
"label": "数据看板",
|
"label": "数据看板",
|
||||||
"url": "{{entire_url('/dashboard_for_sage')}}"
|
"url": "{{entire_url('/dashboard_for_sage')}}"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "hotspot",
|
|
||||||
"label": "热点雷达",
|
|
||||||
"url": "{{entire_url('hotspot')}}"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "agent",
|
"name": "agent",
|
||||||
"label": "代理",
|
"label": "代理",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user