monitor-app 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
monitor_app/app.py ADDED
@@ -0,0 +1,87 @@
1
+ import click
2
+ import os
3
+ import subprocess # ✅ `csv_to_db.py` を実行するために追加
4
+ from flask import Flask, render_template, abort
5
+ from flask_cors import CORS
6
+ from flask_sqlalchemy import SQLAlchemy
7
+ from sqlalchemy import text
8
+ from monitor_app.config.config import (
9
+ SQLALCHEMY_DATABASE_URI,
10
+ SQLALCHEMY_TRACK_MODIFICATIONS,
11
+ ALLOWED_TABLES,
12
+ APP_TITLE,
13
+ HEADER_TEXT,
14
+ FOOTER_TEXT,
15
+ FAVICON_PATH, # ✅ Favicon を追加
16
+ TABLE_CELL_STYLES,
17
+ )
18
+
19
+ app = Flask(__name__)
20
+ CORS(app)
21
+
22
+ # 設定を `config.py` から読み込む
23
+ app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
24
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = SQLALCHEMY_TRACK_MODIFICATIONS
25
+
26
+ db = SQLAlchemy(app)
27
+
28
+
29
+ @app.route("/")
30
+ def index():
31
+ """トップページ"""
32
+ tables = list(ALLOWED_TABLES.keys()) # 📌 許可されたテーブルのみ表示
33
+ return render_template(
34
+ "index.html",
35
+ tables=tables,
36
+ app_title=APP_TITLE,
37
+ header_text=HEADER_TEXT,
38
+ footer_text=FOOTER_TEXT,
39
+ favicon_path=FAVICON_PATH, # ✅ Favicon を追加
40
+ title=APP_TITLE, # ✅ タイトルを設定
41
+ )
42
+
43
+
44
+ @app.route("/table/<table_name>")
45
+ def show_table(table_name):
46
+ """指定されたテーブルのデータを表示"""
47
+ if table_name not in ALLOWED_TABLES: # 📌 許可されていないテーブルは 404
48
+ abort(404)
49
+
50
+ table_info = ALLOWED_TABLES[table_name]
51
+
52
+ # 📌 `join` 設定があれば JOIN クエリを実行
53
+ query = (
54
+ text(table_info["join"])
55
+ if "join" in table_info
56
+ else text(f"SELECT * FROM {table_name}")
57
+ )
58
+
59
+ result = db.session.execute(query)
60
+ columns = result.keys()
61
+ data = [dict(zip(columns, row)) for row in result.fetchall()]
62
+
63
+ return render_template(
64
+ "table.html",
65
+ table_name=table_name,
66
+ columns=columns,
67
+ data=data,
68
+ cell_styles=TABLE_CELL_STYLES,
69
+ app_title=APP_TITLE,
70
+ header_text=HEADER_TEXT,
71
+ footer_text=FOOTER_TEXT,
72
+ favicon_path=FAVICON_PATH, # ✅ Favicon を追加
73
+ title=f"{table_name} - {APP_TITLE}", # ✅ タイトルを設定
74
+ )
75
+
76
+
77
+ def run_server(host="0.0.0.0", port=9990, debug=False):
78
+ """Flask Web アプリを起動"""
79
+ app.run(host=host, port=port, debug=debug)
80
+
81
+
82
+ def main():
83
+ run_server()
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
monitor_app/cli.py ADDED
@@ -0,0 +1,180 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import click
5
+ import toml
6
+ from monitor_app.app import run_server # `app.py` の `run_server()` を直接呼び出す
7
+
8
+ # TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "monitor_app")
9
+ PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
10
+ MONITOR_APP_DIR = os.path.dirname(__file__)
11
+ CONFIG_DIR = os.path.join(MONITOR_APP_DIR, "config")
12
+ CSV_DIR = os.path.join(MONITOR_APP_DIR, "csv")
13
+ TEMPLATES_DIR = os.path.join(MONITOR_APP_DIR, "templates")
14
+ STATIC_DIR = os.path.join(MONITOR_APP_DIR, "static")
15
+ STATIC_CSS_DIR = os.path.join(STATIC_DIR, "css")
16
+ STATIC_JS_DIR = os.path.join(STATIC_DIR, "js")
17
+ PYPROJECT_TEMPLATE = os.path.join(MONITOR_APP_DIR, "pyproject.toml")
18
+
19
+
20
+ @click.group()
21
+ def cli():
22
+ """Monitor App CLI ツール"""
23
+ pass
24
+
25
+
26
+ @click.command()
27
+ @click.argument("project_name")
28
+ def startproject(project_name):
29
+ """新しい Monitor App プロジェクトを作成"""
30
+ project_path = os.path.abspath(project_name)
31
+
32
+ if os.path.exists(project_path):
33
+ click.echo(f"⚠️ 既に '{project_name}' が存在します!")
34
+ return
35
+
36
+ # 📂 プロジェクトフォルダ作成
37
+ os.makedirs(project_path)
38
+
39
+ # 📂 monitor_app アプリフォルダ作成
40
+ DEST_MONITOR_APP_DIR = os.path.join(project_path, "monitor_app")
41
+ DEST_PARENT_DIR = os.path.dirname(DEST_MONITOR_APP_DIR)
42
+ DEST_CONFIG_DIR = os.path.join(DEST_MONITOR_APP_DIR, "config")
43
+ DEST_CSV_DIR = os.path.join(DEST_MONITOR_APP_DIR, "csv")
44
+ DEST_INSTANCES_DIR = os.path.join(DEST_MONITOR_APP_DIR, "instances")
45
+ DEST_TEMPLATES_DIR = os.path.join(DEST_MONITOR_APP_DIR, "templates")
46
+ DEST_STATIC_DIR = os.path.join(DEST_MONITOR_APP_DIR, "static")
47
+ DEST_STATIC_CSS_DIR = os.path.join(DEST_STATIC_DIR, "css")
48
+ DEST_STATIC_JS_DIR = os.path.join(DEST_STATIC_DIR, "js")
49
+ os.makedirs(DEST_MONITOR_APP_DIR)
50
+
51
+ # 📂 CSV・インスタンスフォルダ作成
52
+ os.makedirs(DEST_CSV_DIR, exist_ok=True)
53
+ os.makedirs(DEST_CONFIG_DIR, exist_ok=True)
54
+ os.makedirs(DEST_INSTANCES_DIR, exist_ok=True)
55
+ os.makedirs(DEST_TEMPLATES_DIR, exist_ok=True)
56
+ os.makedirs(DEST_STATIC_JS_DIR, exist_ok=True)
57
+ os.makedirs(DEST_STATIC_CSS_DIR, exist_ok=True)
58
+
59
+ # 📄 `project_template` からファイルをコピー
60
+ monitor_app_files = ["app.py", "cli.py", "csv_to_db.py"]
61
+ for file in monitor_app_files:
62
+ src_path = os.path.join(MONITOR_APP_DIR, file)
63
+ dest_path = os.path.join(DEST_MONITOR_APP_DIR, file)
64
+ if os.path.exists(src_path):
65
+ shutil.copy(src_path, dest_path)
66
+ else:
67
+ click.echo(
68
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
69
+ )
70
+
71
+ config_files = ["config.py"]
72
+ for file in config_files:
73
+ src_path = os.path.join(CONFIG_DIR, file)
74
+ dest_path = os.path.join(DEST_CONFIG_DIR, file)
75
+ if os.path.exists(src_path):
76
+ shutil.copy(src_path, dest_path)
77
+ else:
78
+ click.echo(
79
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
80
+ )
81
+
82
+ templates_files = ["base.html", "index.html", "table.html"]
83
+ for file in templates_files:
84
+ src_path = os.path.join(TEMPLATES_DIR, file)
85
+ dest_path = os.path.join(DEST_TEMPLATES_DIR, file)
86
+ if os.path.exists(src_path):
87
+ shutil.copy(src_path, dest_path)
88
+ else:
89
+ click.echo(
90
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
91
+ )
92
+
93
+ css_files = ["bootstrap.min.css"]
94
+ for file in css_files:
95
+ src_path = os.path.join(STATIC_CSS_DIR, file)
96
+ dest_path = os.path.join(DEST_STATIC_CSS_DIR, file)
97
+ if os.path.exists(src_path):
98
+ shutil.copy(src_path, dest_path)
99
+ else:
100
+ click.echo(
101
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
102
+ )
103
+
104
+ js_files = ["bootstrap.bundle.min.js"]
105
+ for file in js_files:
106
+ src_path = os.path.join(STATIC_JS_DIR, file)
107
+ dest_path = os.path.join(DEST_STATIC_JS_DIR, file)
108
+ if os.path.exists(src_path):
109
+ shutil.copy(src_path, dest_path)
110
+ else:
111
+ click.echo(
112
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
113
+ )
114
+
115
+ # 📄 Favicon コピー
116
+ favicon_src = os.path.join(MONITOR_APP_DIR, "static/favicon.ico")
117
+ favicon_dest = os.path.join(DEST_MONITOR_APP_DIR, "static/favicon.ico")
118
+ if os.path.exists(favicon_src):
119
+ shutil.copy(favicon_src, favicon_dest)
120
+ else:
121
+ click.echo(
122
+ "⚠️ favicon.ico が `project_template` に見つかりません。スキップします。"
123
+ )
124
+
125
+ parent_files = ["pyproject.toml"]
126
+ for file in parent_files:
127
+ src_path = os.path.join(PARENT_DIR, file)
128
+ dest_path = os.path.join(DEST_PARENT_DIR, file)
129
+ if os.path.exists(src_path):
130
+ shutil.copy(src_path, dest_path)
131
+ else:
132
+ click.echo(
133
+ f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
134
+ )
135
+
136
+
137
+ def run_command(command_list):
138
+ """
139
+ 📌 poetry がインストールされていれば `poetry run` を使用し、なければ `python` を使用
140
+ """
141
+ if shutil.which("poetry"):
142
+ command_list.insert(0, "poetry")
143
+ command_list.insert(1, "run")
144
+ else:
145
+ command_list.insert(0, "python")
146
+
147
+ subprocess.run(command_list, check=True)
148
+
149
+
150
+ @click.command()
151
+ @click.option("--host", default="0.0.0.0", help="ホストアドレス")
152
+ @click.option("--port", default=9990, help="ポート番号")
153
+ @click.option("--csv", is_flag=True, help="CSV をデータベースに登録してから起動")
154
+ @click.option("--debug", is_flag=True, help="デバッグモードを有効化")
155
+ def runserver(host, port, csv, debug):
156
+ """Flask Web アプリを起動"""
157
+
158
+ if csv:
159
+ click.echo("🔄 CSV をデータベースに登録中...")
160
+ run_command(["monitor_app/csv_to_db.py"])
161
+ click.echo("✅ CSV 登録完了!アプリを起動します...")
162
+
163
+ click.echo(f"🚀 Web アプリを {host}:{port} で起動")
164
+ run_server(host=host, port=port, debug=debug) # `run_server()` を直接呼び出す
165
+
166
+
167
+ @click.command()
168
+ def import_csv():
169
+ """CSV をデータベースにインポート"""
170
+ click.echo("📂 CSV をデータベースに登録中...")
171
+ run_command(["monitor_app/csv_to_db.py"])
172
+ click.echo("✅ CSV 登録完了!")
173
+
174
+
175
+ cli.add_command(startproject)
176
+ cli.add_command(runserver)
177
+ cli.add_command(import_csv)
178
+
179
+ if __name__ == "__main__":
180
+ cli()
@@ -0,0 +1,95 @@
1
+ import os
2
+
3
+ # 📌 プロジェクトのルートディレクトリ
4
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5
+
6
+ # 📌 インスタンスディレクトリ(データベースの保存先)
7
+ INSTANCE_DIR = os.path.join(BASE_DIR, "instances")
8
+ os.makedirs(INSTANCE_DIR, exist_ok=True)
9
+
10
+ # 📌 使用するデータベースの種類(sqlite, mysql, postgresql)
11
+ DB_TYPE = "sqlite"
12
+
13
+ # 📌 SQLite のカスタムパス設定(None にするとデフォルトを使用)
14
+ CUSTOM_SQLITE_DB_PATH = None
15
+
16
+ # 📌 CSV ファイルの保存ディレクトリ
17
+ DEFAULT_CSV_DIR = os.path.join(BASE_DIR, "csv")
18
+ CUSTOM_CSV_DIR = None
19
+
20
+ CSV_DIR = os.path.abspath(CUSTOM_CSV_DIR) if CUSTOM_CSV_DIR else DEFAULT_CSV_DIR
21
+ os.makedirs(CSV_DIR, exist_ok=True)
22
+
23
+ # 📌 各データベースの接続設定
24
+ if DB_TYPE == "sqlite":
25
+ if CUSTOM_SQLITE_DB_PATH:
26
+ DB_PATH = os.path.abspath(CUSTOM_SQLITE_DB_PATH)
27
+ else:
28
+ DB_PATH = os.path.join(INSTANCE_DIR, "database.db")
29
+
30
+ SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_PATH}"
31
+
32
+ elif DB_TYPE == "mysql":
33
+ MYSQL_USER = "root"
34
+ MYSQL_PASSWORD = "password"
35
+ MYSQL_HOST = "localhost"
36
+ MYSQL_PORT = "3306"
37
+ MYSQL_DB = "monitor_app"
38
+
39
+ SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}"
40
+
41
+ elif DB_TYPE == "postgresql":
42
+ POSTGRES_USER = "postgres"
43
+ POSTGRES_PASSWORD = "password"
44
+ POSTGRES_HOST = "localhost"
45
+ POSTGRES_PORT = "5432"
46
+ POSTGRES_DB = "monitor_app"
47
+
48
+ SQLALCHEMY_DATABASE_URI = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
49
+
50
+ else:
51
+ raise ValueError(f"Unsupported DB_TYPE: {DB_TYPE}")
52
+
53
+ # 📌 SQLAlchemy 設定
54
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
55
+
56
+ # 📌 許可されたテーブルのみを表示
57
+ ALLOWED_TABLES = {
58
+ "users": {"columns": ["id", "name", "email"], "primary_key": "id"},
59
+ "products": {"columns": ["id", "name", "price"], "primary_key": "id"},
60
+ "orders": {
61
+ "columns": ["id", "user_id", "product_id", "amount"],
62
+ "primary_key": "id",
63
+ "foreign_keys": {"user_id": "users.id", "product_id": "products.id"},
64
+ "join": """
65
+ SELECT orders.id, users.name AS user_name, products.name AS product_name, orders.amount
66
+ FROM orders
67
+ JOIN users ON orders.user_id = users.id
68
+ JOIN products ON orders.product_id = products.id
69
+ """,
70
+ },
71
+ }
72
+
73
+ # 📌 ヘッダーとフッターの設定
74
+ APP_TITLE = "Monitor App"
75
+ HEADER_TEXT = "📊 Monitor Dashboard"
76
+ FOOTER_TEXT = "© 2025 Monitor App - Powered by Flask & Bootstrap"
77
+ FAVICON_PATH = "favicon.ico"
78
+
79
+ # 📌 **テーブルセルのスタイル**
80
+ TABLE_CELL_STYLES = {
81
+ "orders": {
82
+ "amount": {
83
+ "greater_than": {"value": 10, "class": "bg-danger text-white"},
84
+ "less_than": {"value": 5, "class": "bg-warning text-dark"},
85
+ "equal_to": {"value": 7, "class": "bg-success text-white"},
86
+ }
87
+ },
88
+ "products": {
89
+ "price": {
90
+ "greater_than": {"value": 1000, "class": "bg-primary text-white"},
91
+ "less_than": {"value": 500, "class": "bg-info text-dark"},
92
+ "equal_to": {"value": 750, "class": "bg-secondary text-white"},
93
+ }
94
+ },
95
+ }
@@ -0,0 +1,5 @@
1
+ id,name,value,data_table
2
+ 1,a,0,0
3
+ 2,b,12,12
4
+ 3,c,3,3
5
+ 4,d,3,4
@@ -0,0 +1,5 @@
1
+ id,name,value,data_table
2
+ 1,a,10,10
3
+ 2,b,12,12
4
+ 3,c,3,3
5
+ 4,d,3,4
@@ -0,0 +1,5 @@
1
+ id,name,value,data_table_2
2
+ 1,a,10,10
3
+ 2,b,12,12
4
+ 3,c,3,3
5
+ 4,d,3,4
@@ -0,0 +1,6 @@
1
+ id,user_id,product_id,amount
2
+ 1,1,1,2
3
+ 2,2,3,1
4
+ 3,1,2,5
5
+ 4,3,1,12
6
+ 5,2,2,4
@@ -0,0 +1,4 @@
1
+ id,name,price
2
+ 1,Apple,10000
3
+ 2,Banana,50
4
+ 3,Cherry,800
@@ -0,0 +1,6 @@
1
+ id,name,email
2
+ 1,John Doe,john@example.com
3
+ 2,Jane Smith,jane@example.com
4
+ 3,Robert Brown,robert@example.com
5
+ 4,Emily Davis,emily@example.com
6
+ 5,Michael Wilson,michael@example.com
@@ -0,0 +1,85 @@
1
+ import os
2
+ import pandas as pd
3
+ from sqlalchemy import (
4
+ create_engine,
5
+ MetaData,
6
+ Table,
7
+ Column,
8
+ Integer,
9
+ String,
10
+ Float,
11
+ ForeignKey,
12
+ )
13
+ from monitor_app.config.config import (
14
+ SQLALCHEMY_DATABASE_URI,
15
+ ALLOWED_TABLES,
16
+ CSV_DIR, # ✅ `CSV_DIR` を `config.py` から取得
17
+ )
18
+
19
+ # データベースエンジンの作成
20
+ engine = create_engine(SQLALCHEMY_DATABASE_URI)
21
+ metadata = MetaData()
22
+
23
+
24
+ def create_tables():
25
+ """ALLOWED_TABLES に基づいてテーブルを作成"""
26
+ for table_name, table_info in ALLOWED_TABLES.items():
27
+ columns = []
28
+
29
+ # プライマリーキーの追加
30
+ primary_key = table_info.get("primary_key")
31
+
32
+ # カラムの型を判定
33
+ for col in table_info["columns"]:
34
+ col_type = String(255) # デフォルトは String
35
+
36
+ if col == primary_key: # プライマリーキー
37
+ col_type = Integer
38
+ elif col.endswith("_id"): # ID系のカラムは Integer 型
39
+ col_type = Integer
40
+ elif "price" in col or "amount" in col: # 金額や数量系のカラムは Float 型
41
+ col_type = Float
42
+
43
+ col_def = Column(col, col_type, primary_key=(col == primary_key))
44
+ columns.append(col_def)
45
+
46
+ # 外部キーの設定
47
+ if "foreign_keys" in table_info:
48
+ for col, ref in table_info["foreign_keys"].items():
49
+ columns.append(Column(col, Integer, ForeignKey(ref)))
50
+
51
+ # テーブルを作成
52
+ Table(table_name, metadata, *columns, extend_existing=True)
53
+
54
+ metadata.create_all(engine) # データベースに適用
55
+ print("✅ テーブルを作成しました")
56
+
57
+
58
+ def import_csv_to_db():
59
+ """CSV をデータベースにインポート"""
60
+ for file in os.listdir(CSV_DIR):
61
+ if file.endswith(".csv"):
62
+ table_name = os.path.splitext(file)[0]
63
+ if (
64
+ table_name in ALLOWED_TABLES
65
+ ): # 📌 `ALLOWED_TABLES` に含まれるもののみ処理
66
+ file_path = os.path.join(CSV_DIR, file)
67
+ df = pd.read_csv(file_path)
68
+
69
+ # データ型を SQLAlchemy のカラム定義と合わせる
70
+ for col in df.columns:
71
+ if col in ALLOWED_TABLES[table_name]["columns"]:
72
+ if col.endswith("_id") or col == ALLOWED_TABLES[table_name].get(
73
+ "primary_key"
74
+ ):
75
+ df[col] = df[col].astype("Int64") # Nullを考慮した整数型
76
+ elif "price" in col or "amount" in col:
77
+ df[col] = df[col].astype(float)
78
+
79
+ df.to_sql(table_name, con=engine, if_exists="replace", index=False)
80
+ print(f"✅ {table_name} に {len(df)} 件のデータを挿入しました")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ create_tables()
85
+ import_csv_to_db()
Binary file
File without changes