Files
ops-manage/backend/models.py
T
Hermes 5eecbecd7f feat: align portal center panel with side cards + redesign login page
- Portal: wrap .panel-head in matching card (border, radius, shadow) with
  cyan->purple left accent bar; h2 28px -> 20px; description right-aligned
  with vertical divider; top edge now aligns with side-menu & links-panel
- Login: switch from absolute-positioned icons to el-input prefix slot;
  layout brand row (logo + title + uppercase subtitle); add form-title
  section with gradient bar; consistent cyan focus; gradient button;
  dashed top divider for back-link; card width 420 -> 460
- Add .gitignore (node_modules, dist, __pycache__, .env, etc.)
2026-07-22 11:12:19 +08:00

154 lines
6.3 KiB
Python

"""SQLAlchemy models for OPS Resource Portal."""
from datetime import datetime, timezone
from extensions import db
def utcnow():
return datetime.now(timezone.utc)
# ---------------------------------------------------------------------------
# Association table: resource <-> option (faceted tagging)
# ---------------------------------------------------------------------------
resource_options = db.Table(
"resource_options",
db.Column("resource_id", db.Integer, db.ForeignKey("resources.id", ondelete="CASCADE"), primary_key=True),
db.Column("option_id", db.Integer, db.ForeignKey("options.id", ondelete="CASCADE"), primary_key=True),
)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False) # bcrypt hash
created_at = db.Column(db.DateTime, default=utcnow)
def set_password(self, raw):
import bcrypt
self.password = bcrypt.hashpw(raw.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
def check_password(self, raw):
import bcrypt
try:
return bcrypt.checkpw(raw.encode("utf-8"), self.password.encode("utf-8"))
except Exception:
return False
class SiteSetting(db.Model):
"""Key-value site configuration editable from the admin panel."""
__tablename__ = "site_settings"
key = db.Column(db.String(64), primary_key=True)
value = db.Column(db.Text, default="")
updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow)
class Category(db.Model):
"""Left-nav menu entry, e.g. 基础设备 / pypi源."""
__tablename__ = "categories"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
icon = db.Column(db.String(16), default="📁") # emoji icon
description = db.Column(db.String(255), default="")
sort_order = db.Column(db.Integer, default=0)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=utcnow)
rows = db.relationship("OptionRow", backref="category", lazy=True,
cascade="all, delete-orphan", order_by="OptionRow.sort_order")
resources = db.relationship("Resource", backref="category", lazy=True,
cascade="all, delete-orphan")
def to_dict(self, with_rows=True):
d = {
"id": self.id, "name": self.name, "icon": self.icon,
"description": self.description, "sort_order": self.sort_order,
"is_active": self.is_active,
"resource_count": len(self.resources),
}
if with_rows:
d["rows"] = [r.to_dict() for r in self.rows]
return d
class OptionRow(db.Model):
"""A selection row inside a category, e.g. 运行环境 / 操作系统."""
__tablename__ = "option_rows"
id = db.Column(db.Integer, primary_key=True)
category_id = db.Column(db.Integer, db.ForeignKey("categories.id", ondelete="CASCADE"), nullable=False)
title = db.Column(db.String(80), nullable=False) # e.g. 运行环境
sort_order = db.Column(db.Integer, default=0)
options = db.relationship("Option", backref="row", lazy=True,
cascade="all, delete-orphan", order_by="Option.sort_order")
def to_dict(self):
return {
"id": self.id, "title": self.title, "sort_order": self.sort_order,
"options": [o.to_dict() for o in self.options],
}
class Option(db.Model):
"""A selectable tile inside a row, e.g. 物理机 / ubuntu."""
__tablename__ = "options"
id = db.Column(db.Integer, primary_key=True)
row_id = db.Column(db.Integer, db.ForeignKey("option_rows.id", ondelete="CASCADE"), nullable=False)
label = db.Column(db.String(80), nullable=False)
icon = db.Column(db.String(16), default="")
sort_order = db.Column(db.Integer, default=0)
def to_dict(self):
return {"id": self.id, "label": self.label, "icon": self.icon, "sort_order": self.sort_order}
class Resource(db.Model):
"""A published internal resource: command / link / text / file."""
__tablename__ = "resources"
id = db.Column(db.Integer, primary_key=True)
category_id = db.Column(db.Integer, db.ForeignKey("categories.id", ondelete="CASCADE"), nullable=False)
title = db.Column(db.String(160), nullable=False)
type = db.Column(db.String(16), nullable=False, default="command") # command|link|text|file
content = db.Column(db.Text, default="") # command text / rich text / note
url = db.Column(db.String(500), default="") # link target / file URL
description = db.Column(db.String(255), default="")
sort_order = db.Column(db.Integer, default=0)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=utcnow)
updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow)
options = db.relationship("Option", secondary=resource_options, lazy="subquery",
backref=db.backref("resources", lazy=True))
def to_dict(self):
return {
"id": self.id, "category_id": self.category_id, "title": self.title,
"type": self.type, "content": self.content, "url": self.url,
"description": self.description, "sort_order": self.sort_order,
"is_active": self.is_active,
"option_ids": sorted(o.id for o in self.options),
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M") if self.created_at else "",
"updated_at": self.updated_at.strftime("%Y-%m-%d %H:%M") if self.updated_at else "",
}
class SideLink(db.Model):
"""Right-panel 其他链接 entry."""
__tablename__ = "side_links"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(160), nullable=False)
url = db.Column(db.String(500), nullable=False)
group_name = db.Column(db.String(80), default="其他链接")
sort_order = db.Column(db.Integer, default=0)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=utcnow)
def to_dict(self):
return {
"id": self.id, "title": self.title, "url": self.url,
"group_name": self.group_name, "sort_order": self.sort_order,
"is_active": self.is_active,
}