awacke1 commited on
Commit
5894b31
Β·
verified Β·
1 Parent(s): c81fcd8

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +266 -0
app.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =============================================================================
2
+ # ───────────── IMPORTS ─────────────
3
+ # =============================================================================
4
+ import base64
5
+ import glob
6
+ import json
7
+ import os
8
+ import pandas as pd
9
+ import pytz
10
+ import re
11
+ import shutil
12
+ import streamlit as st
13
+ import time
14
+ import uuid
15
+ import zipfile
16
+ from azure.cosmos import CosmosClient, PartitionKey, exceptions
17
+ from datetime import datetime
18
+
19
+ # =============================================================================
20
+ # ───────────── APP CONFIGURATION ─────────────
21
+ # =============================================================================
22
+ Site_Name = 'πŸ™ GitCosmos'
23
+ title = "πŸ™ GitCosmos"
24
+ st.set_page_config(
25
+ page_title=title,
26
+ page_icon='πŸ™πŸŒŒπŸ’«',
27
+ layout="wide",
28
+ initial_sidebar_state="auto"
29
+ )
30
+
31
+ # Cosmos DB Configuration
32
+ ENDPOINT = "https://acae-afd.documents.azure.com:443/"
33
+ Key = os.environ.get("Key") # Ensure this is set in your environment
34
+
35
+ # =============================================================================
36
+ # ───────────── HELPER FUNCTIONS ─────────────
37
+ # =============================================================================
38
+ def generate_unique_id():
39
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
40
+ unique_uuid = str(uuid.uuid4())
41
+ return f"{timestamp}-{unique_uuid}"
42
+
43
+ def get_download_link(file_path):
44
+ with open(file_path, "rb") as file:
45
+ contents = file.read()
46
+ b64 = base64.b64encode(contents).decode()
47
+ file_name = os.path.basename(file_path)
48
+ return f'<a href="data:file/txt;base64,{b64}" download="{file_name}">Download {file_name} πŸ“‚</a>'
49
+
50
+ def sanitize_json_text(text):
51
+ text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', text)
52
+ text = text.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
53
+ return text
54
+
55
+ # =============================================================================
56
+ # ───────────── COSMOS DB FUNCTIONS ─────────────
57
+ # =============================================================================
58
+ def get_databases(client):
59
+ return [db['id'] for db in client.list_databases()]
60
+
61
+ def get_containers(database):
62
+ return [container['id'] for container in database.list_containers()]
63
+
64
+ def get_documents(container, limit=None):
65
+ query = "SELECT * FROM c ORDER BY c._ts DESC"
66
+ items = list(container.query_items(query=query, enable_cross_partition_query=True, max_item_count=limit))
67
+ return items
68
+
69
+ def insert_record(container, record):
70
+ try:
71
+ container.create_item(body=record)
72
+ return True, "Inserted! πŸŽ‰"
73
+ except exceptions.CosmosHttpResponseError as e:
74
+ return False, f"HTTP error: {str(e)} 🚨"
75
+ except Exception as e:
76
+ return False, f"Error: {str(e)} 😱"
77
+
78
+ def update_record(container, updated_record):
79
+ try:
80
+ container.upsert_item(body=updated_record)
81
+ return True, f"Updated {updated_record['id']} πŸ› οΈ"
82
+ except exceptions.CosmosHttpResponseError as e:
83
+ return False, f"HTTP error: {str(e)} 🚨"
84
+ except Exception as e:
85
+ return False, f"Error: {str(e)} 😱"
86
+
87
+ def delete_record(container, record):
88
+ try:
89
+ doc_id = record["id"]
90
+ partition_key_value = record.get("pk", doc_id)
91
+ container.delete_item(item=doc_id, partition_key=partition_key_value)
92
+ return True, f"Record {doc_id} deleted. πŸ—‘οΈ"
93
+ except exceptions.CosmosResourceNotFoundError:
94
+ return True, f"Record {doc_id} not found. πŸ—‘οΈ"
95
+ except exceptions.CosmosHttpResponseError as e:
96
+ return False, f"HTTP error: {str(e)} 🚨"
97
+ except Exception as e:
98
+ return False, f"Error: {str(e)} 😱"
99
+
100
+ def archive_current_container(database_name, container_name, client):
101
+ try:
102
+ base_dir = "./cosmos_archive"
103
+ if os.path.exists(base_dir):
104
+ shutil.rmtree(base_dir)
105
+ os.makedirs(base_dir)
106
+ db_client = client.get_database_client(database_name)
107
+ container_client = db_client.get_container_client(container_name)
108
+ items = list(container_client.read_all_items())
109
+ container_dir = os.path.join(base_dir, container_name)
110
+ os.makedirs(container_dir)
111
+ for item in items:
112
+ item_id = item.get('id', f"unknown_{datetime.now().strftime('%Y%m%d%H%M%S')}")
113
+ with open(os.path.join(container_dir, f"{item_id}.json"), 'w') as f:
114
+ json.dump(item, f, indent=2)
115
+ archive_name = f"{container_name}_archive_{datetime.now().strftime('%Y%m%d%H%M%S')}"
116
+ shutil.make_archive(archive_name, 'zip', base_dir)
117
+ return get_download_link(f"{archive_name}.zip")
118
+ except Exception as e:
119
+ return f"Archive error: {str(e)} 😒"
120
+
121
+ def create_new_container(database, container_id, partition_key_path):
122
+ try:
123
+ container = database.create_container(
124
+ id=container_id,
125
+ partition_key=PartitionKey(path=partition_key_path)
126
+ )
127
+ return container
128
+ except exceptions.CosmosResourceExistsError:
129
+ return database.get_container_client(container_id)
130
+ except exceptions.CosmosHttpResponseError as e:
131
+ st.error(f"Error creating container: {str(e)}")
132
+ return None
133
+
134
+ # =============================================================================
135
+ # ───────────── UI FUNCTIONS ─────────────
136
+ # =============================================================================
137
+ def edit_all_documents(container):
138
+ st.markdown("### πŸ“‘ All Documents")
139
+ documents = get_documents(container)
140
+ if not documents:
141
+ st.info("No documents in this container.")
142
+ return
143
+ for doc in documents:
144
+ ts = doc.get("_ts", 0)
145
+ dt = datetime.fromtimestamp(ts) if ts else datetime.now()
146
+ formatted_ts = dt.strftime("%I:%M %p %m/%d/%Y")
147
+ header = f"{doc.get('name', 'Unnamed')} - {formatted_ts}"
148
+ with st.expander(header):
149
+ doc_key = f"editor_{doc['id']}"
150
+ if doc_key not in st.session_state:
151
+ st.session_state[doc_key] = json.dumps(doc, indent=2)
152
+ edited_content = st.text_area("Edit JSON", value=st.session_state[doc_key], height=300, key=doc_key)
153
+ if st.button("πŸ’Ύ Save", key=f"save_{doc['id']}"):
154
+ try:
155
+ updated_doc = json.loads(sanitize_json_text(edited_content))
156
+ # Preserve system fields and identity
157
+ updated_doc['id'] = doc['id']
158
+ updated_doc['pk'] = doc.get('pk', doc['id'])
159
+ for field in ['_ts', '_rid', '_self', '_etag', '_attachments']:
160
+ updated_doc.pop(field, None) # Remove system fields before upsert
161
+ success, message = update_record(container, updated_doc)
162
+ if success:
163
+ st.success(f"Saved {doc['id']}")
164
+ st.session_state[doc_key] = json.dumps(updated_doc, indent=2)
165
+ else:
166
+ st.error(message)
167
+ except json.JSONDecodeError:
168
+ st.error("Invalid JSON format.")
169
+ except Exception as e:
170
+ st.error(f"Save error: {str(e)}")
171
+ if st.button("πŸ—‘οΈ Delete", key=f"delete_{doc['id']}"):
172
+ success, message = delete_record(container, doc)
173
+ if success:
174
+ st.success(message)
175
+ st.rerun()
176
+ else:
177
+ st.error(message)
178
+
179
+ def new_item_default(container):
180
+ new_id = generate_unique_id()
181
+ default_doc = {
182
+ "id": new_id,
183
+ "pk": new_id,
184
+ "name": "New Document",
185
+ "content": "Start editing here...",
186
+ "timestamp": datetime.now().isoformat(),
187
+ "type": "sample"
188
+ }
189
+ success, message = insert_record(container, default_doc)
190
+ if success:
191
+ st.success("New document created! ✨")
192
+ st.rerun()
193
+ else:
194
+ st.error(f"Error creating new item: {message}")
195
+
196
+ # =============================================================================
197
+ # ───────────── MAIN FUNCTION ─────────────
198
+ # =============================================================================
199
+ def main():
200
+ st.title("πŸ™ GitCosmos - CosmosDB Manager")
201
+
202
+ # Initialize session state
203
+ if "client" not in st.session_state:
204
+ if not Key:
205
+ st.error("Missing CosmosDB Key πŸ”‘βŒ")
206
+ return
207
+ st.session_state.client = CosmosClient(ENDPOINT, credential=Key)
208
+ st.session_state.selected_database = None
209
+ st.session_state.selected_container = None
210
+
211
+ # Sidebar: Hierarchical Navigation
212
+ st.sidebar.title("πŸ™ Navigator")
213
+
214
+ # Databases Section
215
+ st.sidebar.subheader("Databases")
216
+ databases = get_databases(st.session_state.client)
217
+ selected_db = st.sidebar.selectbox("πŸ—ƒοΈ Select Database", databases, key="db_select")
218
+ if selected_db != st.session_state.selected_database:
219
+ st.session_state.selected_database = selected_db
220
+ st.session_state.selected_container = None
221
+ st.rerun()
222
+
223
+ # Containers Section
224
+ if st.session_state.selected_database:
225
+ database = st.session_state.client.get_database_client(st.session_state.selected_database)
226
+ st.sidebar.subheader("Containers")
227
+ if st.sidebar.button("πŸ†• New Container"):
228
+ with st.sidebar.form("new_container_form"):
229
+ container_id = st.text_input("Container ID", "new-container")
230
+ partition_key = st.text_input("Partition Key", "/pk")
231
+ if st.form_submit_button("Create"):
232
+ container = create_new_container(database, container_id, partition_key)
233
+ if container:
234
+ st.success(f"Container '{container_id}' created!")
235
+ st.rerun()
236
+ containers = get_containers(database)
237
+ selected_container = st.sidebar.selectbox("πŸ“ Select Container", containers, key="container_select")
238
+ if selected_container != st.session_state.selected_container:
239
+ st.session_state.selected_container = selected_container
240
+ st.rerun()
241
+
242
+ # Actions Section
243
+ st.sidebar.subheader("Actions")
244
+ if st.session_state.selected_container and st.sidebar.button("πŸ“¦ Export Container"):
245
+ download_link = archive_current_container(st.session_state.selected_database, st.session_state.selected_container, st.session_state.client)
246
+ if download_link.startswith('<a'):
247
+ st.sidebar.markdown(download_link, unsafe_allow_html=True)
248
+ else:
249
+ st.sidebar.error(download_link)
250
+
251
+ # Items Section
252
+ st.sidebar.subheader("Items")
253
+ if st.session_state.selected_container:
254
+ if st.sidebar.button("βž• New Item"):
255
+ container = database.get_container_client(st.session_state.selected_container)
256
+ new_item_default(container)
257
+
258
+ # Central Area: Display and Edit All Documents
259
+ if st.session_state.selected_container:
260
+ container = database.get_container_client(st.session_state.selected_container)
261
+ edit_all_documents(container)
262
+ else:
263
+ st.info("Select a database and container to view and edit documents.")
264
+
265
+ if __name__ == "__main__":
266
+ main()