DocUA commited on
Commit
49da158
·
1 Parent(s): 18df3b2
Files changed (5) hide show
  1. Dockerfile +16 -23
  2. app.py +196 -44
  3. start.sh +1 -20
  4. templates/employees.html +236 -0
  5. templates/index.html +97 -43
Dockerfile CHANGED
@@ -1,36 +1,29 @@
1
- FROM mysql:8.0.22
2
 
3
- # Встановлення змінних середовища
4
- ENV MYSQL_ROOT_PASSWORD=root_password
5
- ENV MYSQL_DATABASE=test_db
6
- ENV MYSQL_USER=test_user
7
- ENV MYSQL_PASSWORD=test_password
8
-
9
- # Створення директорії для ініціалізаційних скриптів
10
- RUN mkdir -p /docker-entrypoint-initdb.d
11
 
12
- # Копіювання SQL файлу ініціалізації
13
- COPY init.sql /docker-entrypoint-initdb.d/
14
 
15
- # Встановлення необхідних пакетів
16
- RUN rm -f /etc/apt/sources.list.d/mysql.list && \
17
- apt-get update && \
18
- apt-get install -y wget python3 python3-pip && \
19
- pip3 install --no-cache-dir flask mysql-connector-python && \
20
- apt-get clean && \
21
- rm -rf /var/lib/apt/lists/*
22
 
23
- # Копіювання файлів веб-додатку
24
  COPY app.py /app.py
25
  COPY templates /templates
26
  COPY static /static
27
-
28
- # Скрипт для запуску
29
  COPY start.sh /start.sh
 
 
30
  RUN chmod +x /start.sh
31
 
32
- # Відкриття порту для API
33
  EXPOSE 7860
34
 
35
- # Запуск сервісів
36
  CMD ["/start.sh"]
 
1
+ FROM python:3.9-slim
2
 
3
+ # Встановлення необхідних пакетів
4
+ RUN apt-get update && \
5
+ apt-get install -y --no-install-recommends \
6
+ default-mysql-client \
7
+ && apt-get clean \
8
+ && rm -rf /var/lib/apt/lists/*
 
 
9
 
10
+ # Встановлення бібліотек Python
11
+ RUN pip install --no-cache-dir flask mysql-connector-python
12
 
13
+ # Створення директорій для застосунку
14
+ WORKDIR /app
 
 
 
 
 
15
 
16
+ # Копіювання файлів
17
  COPY app.py /app.py
18
  COPY templates /templates
19
  COPY static /static
 
 
20
  COPY start.sh /start.sh
21
+
22
+ # Права на виконання
23
  RUN chmod +x /start.sh
24
 
25
+ # Відкриття порту
26
  EXPOSE 7860
27
 
28
+ # Запуск веб-інтерфейсу
29
  CMD ["/start.sh"]
app.py CHANGED
@@ -5,14 +5,21 @@ import time
5
 
6
  app = Flask(__name__, template_folder='/templates', static_folder='/static')
7
 
8
- def get_db_connection():
 
 
 
 
 
 
 
 
 
9
  try:
10
- return mysql.connector.connect(
11
- host="localhost",
12
- user="test_user",
13
- password="test_password",
14
- database="test_db"
15
- )
16
  except Exception as e:
17
  print(f"Error connecting to MySQL: {e}")
18
  raise e
@@ -33,18 +40,106 @@ def index():
33
  cursor.execute("SHOW DATABASES")
34
  databases = [db[0] for db in cursor.fetchall()]
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  # Отримуємо списки таблиць
37
- cursor.execute("SHOW TABLES")
38
  tables = [table[0] for table in cursor.fetchall()]
39
 
 
40
  cursor.close()
41
  conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  except Exception as e:
43
- print(f"Error: {e}")
44
- databases = []
45
- tables = []
46
-
47
- return render_template('index.html', status=status, databases=databases, tables=tables)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  @app.route('/execute', methods=['POST'])
50
  def execute_query():
@@ -52,65 +147,122 @@ def execute_query():
52
  if request.content_type == 'application/json':
53
  data = request.json
54
  query = data.get('query', '')
 
55
  else:
56
  query = request.form.get('query', '')
 
57
 
58
  if not query:
59
  return jsonify({'error': 'Запит не може бути порожнім'}), 400
60
 
61
  try:
62
- conn = get_db_connection()
63
  cursor = conn.cursor(dictionary=True)
64
  cursor.execute(query)
65
 
66
  if query.lower().strip().startswith('select'):
67
  result = cursor.fetchall()
68
  conn.close()
69
- return render_template('result.html', results=result, query=query)
 
 
 
 
70
  else:
71
  conn.commit()
72
  affected_rows = cursor.rowcount
73
  conn.close()
74
- return render_template('result.html', affected_rows=affected_rows, query=query)
 
 
 
 
75
 
76
  except Exception as e:
77
  error_msg = str(e)
78
- return render_template('result.html', error=error_msg, query=query)
79
-
80
- @app.route('/download_employees', methods=['GET'])
81
- def download_employees():
82
- return render_template('download.html')
83
 
84
- @app.route('/import_employees', methods=['POST'])
85
- def import_employees():
86
  try:
87
- # Створюємо тимчасову директорію
88
- os.system("mkdir -p /tmp/employees")
89
 
90
- # Завантажуємо базу даних
91
- os.system("cd /tmp/employees && wget -q https://github.com/datacharmer/test_db/archive/master.zip")
92
- os.system("cd /tmp/employees && apt-get update && apt-get install -y unzip && unzip -q master.zip")
93
 
94
- # Імпортуємо базу даних
95
- import_result = os.system("cd /tmp/employees/test_db-master && mysql -u root -proot_password < employees.sql")
 
 
 
 
 
 
 
96
 
97
- # Надаємо права користувачу test_user
98
- grant_result = os.system("mysql -u root -proot_password -e \"GRANT ALL PRIVILEGES ON employees.* TO 'test_user'@'%';\"")
99
- os.system("mysql -u root -proot_password -e \"FLUSH PRIVILEGES;\"")
100
 
101
- # Видаляємо тимчасові файли
102
- os.system("rm -rf /tmp/employees")
 
103
 
104
- if import_result == 0 and grant_result == 0:
105
- return render_template('import_success.html')
106
- else:
107
- return render_template('import_error.html',
108
- error=f"Import result: {import_result}, Grant result: {grant_result}")
109
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  except Exception as e:
111
- return render_template('import_error.html', error=str(e))
112
 
113
  if __name__ == '__main__':
114
- # Чекаємо кілька секунд для повного запуску MySQL
115
- time.sleep(5)
116
  app.run(host='0.0.0.0', port=7860)
 
5
 
6
  app = Flask(__name__, template_folder='/templates', static_folder='/static')
7
 
8
+ # Параметри підключення до віддаленої бази даних
9
+ DB_CONFIG = {
10
+ "host": "mysql-emploees-zabolotniua-5b91.c.aivencloud.com",
11
+ "port": 12374,
12
+ "user": "avnadmin",
13
+ "password": "AVNS_rccbF_so2YPvPk3zg0z",
14
+ "ssl_mode": "REQUIRED"
15
+ }
16
+
17
+ def get_db_connection(database=None):
18
  try:
19
+ config = DB_CONFIG.copy()
20
+ if database:
21
+ config["database"] = database
22
+ return mysql.connector.connect(**config)
 
 
23
  except Exception as e:
24
  print(f"Error connecting to MySQL: {e}")
25
  raise e
 
40
  cursor.execute("SHOW DATABASES")
41
  databases = [db[0] for db in cursor.fetchall()]
42
 
43
+ cursor.close()
44
+ conn.close()
45
+
46
+ # Перевіряємо наявність бази даних employees
47
+ employees_exists = "employees" in databases
48
+
49
+ return render_template('index.html',
50
+ status=status,
51
+ databases=databases,
52
+ tables=[],
53
+ employees_exists=employees_exists,
54
+ remote_connection=True,
55
+ connection_info=DB_CONFIG)
56
+ except Exception as e:
57
+ print(f"Error: {e}")
58
+ return render_template('index.html',
59
+ status={"status": "offline", "error": str(e)},
60
+ databases=[],
61
+ tables=[],
62
+ employees_exists=False,
63
+ remote_connection=True,
64
+ connection_info=DB_CONFIG)
65
+
66
+ @app.route('/database/<database>')
67
+ def show_database(database):
68
+ try:
69
+ conn = get_db_connection(database)
70
+ cursor = conn.cursor()
71
+
72
  # Отримуємо списки таблиць
73
+ cursor.execute(f"SHOW TABLES FROM `{database}`")
74
  tables = [table[0] for table in cursor.fetchall()]
75
 
76
+ # Закриваємо з'єднання і створюємо нове для кожної таблиці
77
  cursor.close()
78
  conn.close()
79
+
80
+ # Отримуємо інформацію про таблиці
81
+ table_info = []
82
+
83
+ for table in tables:
84
+ try:
85
+ conn = get_db_connection(database)
86
+ cursor = conn.cursor()
87
+
88
+ # Отримуємо кількість рядків
89
+ cursor.execute(f"SELECT COUNT(*) FROM `{database}`.`{table}`")
90
+ count = cursor.fetchone()[0]
91
+
92
+ table_info.append({
93
+ 'name': table,
94
+ 'count': count,
95
+ 'size': 'N/A' # Розмір не можемо отримати з віддаленої бази
96
+ })
97
+
98
+ cursor.close()
99
+ conn.close()
100
+ except Exception as e:
101
+ print(f"Error getting info for table {table}: {e}")
102
+ table_info.append({
103
+ 'name': table,
104
+ 'count': 'Error',
105
+ 'size': 'Error'
106
+ })
107
+
108
+ return render_template('database.html',
109
+ database=database,
110
+ tables=table_info,
111
+ remote_connection=True)
112
  except Exception as e:
113
+ return render_template('error.html', error=str(e))
114
+
115
+ @app.route('/table/<database>/<table_name>')
116
+ def show_table(database, table_name):
117
+ try:
118
+ conn = get_db_connection(database)
119
+ cursor = conn.cursor(dictionary=True)
120
+
121
+ # Отримуємо структуру таблиці
122
+ cursor.execute(f"DESCRIBE `{table_name}`")
123
+ columns = cursor.fetchall()
124
+
125
+ # Отримуємо дані таблиці
126
+ cursor.execute(f"SELECT * FROM `{table_name}` LIMIT 100")
127
+ data = cursor.fetchall()
128
+
129
+ # Отримуємо загальну кількість рядків у таблиці
130
+ cursor.execute(f"SELECT COUNT(*) as count FROM `{table_name}`")
131
+ total_count = cursor.fetchone()['count']
132
+
133
+ conn.close()
134
+ return render_template('table.html',
135
+ database=database,
136
+ table_name=table_name,
137
+ columns=columns,
138
+ data=data,
139
+ total_count=total_count,
140
+ remote_connection=True)
141
+ except Exception as e:
142
+ return render_template('error.html', error=str(e))
143
 
144
  @app.route('/execute', methods=['POST'])
145
  def execute_query():
 
147
  if request.content_type == 'application/json':
148
  data = request.json
149
  query = data.get('query', '')
150
+ database = data.get('database', 'employees')
151
  else:
152
  query = request.form.get('query', '')
153
+ database = request.form.get('database', 'employees')
154
 
155
  if not query:
156
  return jsonify({'error': 'Запит не може бути порожнім'}), 400
157
 
158
  try:
159
+ conn = get_db_connection(database)
160
  cursor = conn.cursor(dictionary=True)
161
  cursor.execute(query)
162
 
163
  if query.lower().strip().startswith('select'):
164
  result = cursor.fetchall()
165
  conn.close()
166
+ return render_template('result.html',
167
+ results=result,
168
+ query=query,
169
+ database=database,
170
+ remote_connection=True)
171
  else:
172
  conn.commit()
173
  affected_rows = cursor.rowcount
174
  conn.close()
175
+ return render_template('result.html',
176
+ affected_rows=affected_rows,
177
+ query=query,
178
+ database=database,
179
+ remote_connection=True)
180
 
181
  except Exception as e:
182
  error_msg = str(e)
183
+ return render_template('result.html',
184
+ error=error_msg,
185
+ query=query,
186
+ database=database,
187
+ remote_connection=True)
188
 
189
+ @app.route('/employees')
190
+ def employees_info():
191
  try:
192
+ conn = get_db_connection('employees')
193
+ cursor = conn.cursor(dictionary=True)
194
 
195
+ # Отримуємо інформацію про таблиці
196
+ cursor.execute("SHOW TABLES FROM employees")
197
+ tables = [table['Tables_in_employees'] for table in cursor.fetchall()]
198
 
199
+ tables_info = []
200
+ for table in tables:
201
+ cursor.execute(f"SELECT COUNT(*) as count FROM employees.{table}")
202
+ count = cursor.fetchone()['count']
203
+ tables_info.append({
204
+ 'table_name': table,
205
+ 'employee_count': count,
206
+ 'total_size_mb': 'N/A' # Не можемо отримати розмір з віддаленої бази
207
+ })
208
 
209
+ # Отримуємо загальну кількість співробітників
210
+ cursor.execute("SELECT COUNT(*) as count FROM employees")
211
+ total_employees = cursor.fetchone()['count']
212
 
213
+ # Отримуємо кількість департаментів
214
+ cursor.execute("SELECT COUNT(*) as count FROM departments")
215
+ total_departments = cursor.fetchone()['count']
216
 
217
+ # Отримуємо діапазон дат
218
+ cursor.execute("SELECT MIN(hire_date) as min_date, MAX(hire_date) as max_date FROM employees")
219
+ date_range = cursor.fetchone()
220
+
221
+ # Отримуємо кількість чоловіків і жінок
222
+ cursor.execute("""
223
+ SELECT
224
+ gender,
225
+ COUNT(*) as count,
226
+ ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM employees), 2) as percentage
227
+ FROM
228
+ employees
229
+ GROUP BY
230
+ gender
231
+ """)
232
+ gender_stats = cursor.fetchall()
233
+
234
+ # Отримуємо топ департаментів за кількістю співробітників
235
+ cursor.execute("""
236
+ SELECT
237
+ d.dept_name,
238
+ COUNT(de.emp_no) as employee_count
239
+ FROM
240
+ departments d
241
+ JOIN
242
+ dept_emp de ON d.dept_no = de.dept_no
243
+ WHERE
244
+ de.to_date = '9999-01-01'
245
+ GROUP BY
246
+ d.dept_name
247
+ ORDER BY
248
+ employee_count DESC
249
+ LIMIT 5
250
+ """)
251
+ top_departments = cursor.fetchall()
252
+
253
+ conn.close()
254
+
255
+ return render_template('employees.html',
256
+ tables_info=tables_info,
257
+ total_employees=total_employees,
258
+ total_departments=total_departments,
259
+ date_range=date_range,
260
+ gender_stats=gender_stats,
261
+ top_departments=top_departments,
262
+ remote_connection=True)
263
  except Exception as e:
264
+ return render_template('error.html', error=str(e))
265
 
266
  if __name__ == '__main__':
267
+ # Запуск веб-сервера
 
268
  app.run(host='0.0.0.0', port=7860)
start.sh CHANGED
@@ -1,24 +1,5 @@
1
  #!/bin/bash
2
 
3
- # Запуск MySQL у фоновому режимі
4
- echo "Запуск MySQL..."
5
- /entrypoint.sh mysqld &
6
-
7
- # Чекаємо, поки MySQL запуститься
8
- echo "Очікування запуску MySQL..."
9
- sleep 15
10
-
11
- # Перевірка, чи запустився MySQL
12
- echo "Перевірка стану MySQL..."
13
- for i in {1..5}; do
14
- if mysqladmin ping -h localhost -u root -proot_password --silent; then
15
- echo "MySQL успішно запущено"
16
- break
17
- fi
18
- echo "Очікування..."
19
- sleep 3
20
- done
21
-
22
  # Запуск Flask API
23
- echo "Запуск API на порту 7860..."
24
  python3 /app.py
 
1
  #!/bin/bash
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  # Запуск Flask API
4
+ echo "Запуск веб-інтерфейсу на порту 7860..."
5
  python3 /app.py
templates/employees.html ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uk">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>База даних Employees</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
8
+ <style>
9
+ body {
10
+ font-family: Arial, sans-serif;
11
+ padding: 20px;
12
+ }
13
+ .card {
14
+ margin-bottom: 20px;
15
+ border-radius: 8px;
16
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
17
+ }
18
+ .stats-card {
19
+ background-color: #f8f9fa;
20
+ border-radius: 8px;
21
+ padding: 15px;
22
+ margin-bottom: 15px;
23
+ }
24
+ .big-number {
25
+ font-size: 2rem;
26
+ font-weight: bold;
27
+ color: #007bff;
28
+ }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <div class="container">
33
+ <div class="d-flex justify-content-between align-items-center mb-4">
34
+ <h1>База даних "Employees"</h1>
35
+ <a href="/" class="btn btn-primary">На головну</a>
36
+ </div>
37
+
38
+ <!-- Загальні статистики -->
39
+ <div class="card mb-4">
40
+ <div class="card-header bg-primary text-white">
41
+ <h5>Загальна інформація</h5>
42
+ </div>
43
+ <div class="card-body">
44
+ <div class="row">
45
+ <div class="col-md-4">
46
+ <div class="stats-card text-center">
47
+ <div class="big-number">{{ total_employees }}</div>
48
+ <div>Співробітників</div>
49
+ </div>
50
+ </div>
51
+ <div class="col-md-4">
52
+ <div class="stats-card text-center">
53
+ <div class="big-number">{{ total_departments }}</div>
54
+ <div>Департаментів</div>
55
+ </div>
56
+ </div>
57
+ <div class="col-md-4">
58
+ <div class="stats-card text-center">
59
+ <div class="big-number">{{ tables_info|length }}</div>
60
+ <div>Таблиць</div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <div class="mt-3">
66
+ <h5>Діапазон дат найму</h5>
67
+ <p>З {{ date_range.min_date }} по {{ date_range.max_date }}</p>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Гендерна статистика -->
73
+ <div class="card mb-4">
74
+ <div class="card-header bg-info text-white">
75
+ <h5>Гендерний розподіл</h5>
76
+ </div>
77
+ <div class="card-body">
78
+ <div class="row">
79
+ {% for stat in gender_stats %}
80
+ <div class="col-md-6">
81
+ <div class="stats-card text-center">
82
+ <h5>{{ 'Чоловіки' if stat.gender == 'M' else 'Жінки' }}</h5>
83
+ <div class="big-number">{{ stat.count }}</div>
84
+ <div>{{ stat.percentage }}% від загальної кількості</div>
85
+ <div class="progress mt-2">
86
+ <div class="progress-bar {{ 'bg-primary' if stat.gender == 'M' else 'bg-danger' }}"
87
+ role="progressbar"
88
+ style="width: {{ stat.percentage }}%"
89
+ aria-valuenow="{{ stat.percentage }}"
90
+ aria-valuemin="0"
91
+ aria-valuemax="100">{{ stat.percentage }}%</div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ {% endfor %}
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Топ департаментів -->
101
+ <div class="card mb-4">
102
+ <div class="card-header bg-success text-white">
103
+ <h5>Топ департаментів за кількістю співробітників</h5>
104
+ </div>
105
+ <div class="card-body">
106
+ <div class="table-responsive">
107
+ <table class="table table-striped">
108
+ <thead>
109
+ <tr>
110
+ <th>Департамент</th>
111
+ <th>Кількість співробітників</th>
112
+ </tr>
113
+ </thead>
114
+ <tbody>
115
+ {% for dept in top_departments %}
116
+ <tr>
117
+ <td>{{ dept.dept_name }}</td>
118
+ <td>{{ dept.employee_count }}</td>
119
+ </tr>
120
+ {% endfor %}
121
+ </tbody>
122
+ </table>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <!-- Інформація про таблиці -->
128
+ <div class="card mb-4">
129
+ <div class="card-header bg-secondary text-white">
130
+ <h5>Таблиці в базі даних</h5>
131
+ </div>
132
+ <div class="card-body p-0">
133
+ <div class="table-responsive">
134
+ <table class="table table-striped table-bordered mb-0">
135
+ <thead class="table-dark">
136
+ <tr>
137
+ <th>Назва таблиці</th>
138
+ <th>Кількість рядків</th>
139
+ <th>Дії</th>
140
+ </tr>
141
+ </thead>
142
+ <tbody>
143
+ {% for table in tables_info %}
144
+ <tr>
145
+ <td>{{ table.table_name }}</td>
146
+ <td>{{ table.employee_count }}</td>
147
+ <td>
148
+ <a href="/table/employees/{{ table.table_name }}" class="btn btn-sm btn-primary">Переглянути</a>
149
+ <button class="btn btn-sm btn-success" onclick="copyToQueryConsole('SELECT * FROM {{ table.table_name }} LIMIT 10;')">Запит SELECT</button>
150
+ </td>
151
+ </tr>
152
+ {% endfor %}
153
+ </tbody>
154
+ </table>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <!-- Приклади запитів -->
160
+ <div class="card">
161
+ <div class="card-header bg-dark text-white">
162
+ <h5>Корисні SQL запити</h5>
163
+ </div>
164
+ <div class="card-body">
165
+ <div class="mb-3">
166
+ <h6>1. Середня зарплата за департаментами</h6>
167
+ <pre><code>SELECT d.dept_name, ROUND(AVG(s.salary), 2) as avg_salary
168
+ FROM salaries s
169
+ JOIN dept_emp de ON s.emp_no = de.emp_no
170
+ JOIN departments d ON de.dept_no = d.dept_no
171
+ WHERE s.to_date = '9999-01-01' AND de.to_date = '9999-01-01'
172
+ GROUP BY d.dept_name
173
+ ORDER BY avg_salary DESC;</code></pre>
174
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT d.dept_name, ROUND(AVG(s.salary), 2) as avg_salary FROM salaries s JOIN dept_emp de ON s.emp_no = de.emp_no JOIN departments d ON de.dept_no = d.dept_no WHERE s.to_date = \'9999-01-01\' AND de.to_date = \'9999-01-01\' GROUP BY d.dept_name ORDER BY avg_salary DESC;')">Копіювати до консолі</button>
175
+ </div>
176
+
177
+ <div class="mb-3">
178
+ <h6>2. Топ-10 найвищеоплачуваних співробітників</h6>
179
+ <pre><code>SELECT e.first_name, e.last_name, s.salary, d.dept_name
180
+ FROM employees e
181
+ JOIN salaries s ON e.emp_no = s.emp_no
182
+ JOIN dept_emp de ON e.emp_no = de.emp_no
183
+ JOIN departments d ON de.dept_no = d.dept_no
184
+ WHERE s.to_date = '9999-01-01' AND de.to_date = '9999-01-01'
185
+ ORDER BY s.salary DESC
186
+ LIMIT 10;</code></pre>
187
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT e.first_name, e.last_name, s.salary, d.dept_name FROM employees e JOIN salaries s ON e.emp_no = s.emp_no JOIN dept_emp de ON e.emp_no= de.emp_no JOIN departments d ON de.dept_no = d.dept_no WHERE s.to_date = \'9999-01-01\' AND de.to_date = \'9999-01-01\' ORDER BY s.salary DESC LIMIT 10;')">Копіювати до консолі</button>
188
+ </div>
189
+
190
+ <div class="mb-3">
191
+ <h6>3. Кількість співробітників за роками найму</h6>
192
+ <pre><code>SELECT
193
+ YEAR(hire_date) as hire_year,
194
+ COUNT(*) as employee_count
195
+ FROM
196
+ employees
197
+ GROUP BY
198
+ hire_year
199
+ ORDER BY
200
+ hire_year;</code></pre>
201
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT YEAR(hire_date) as hire_year, COUNT(*) as employee_count FROM employees GROUP BY hire_year ORDER BY hire_year;')">Копіювати до консолі</button>
202
+ </div>
203
+
204
+ <div>
205
+ <h6>4. Менеджери департаментів</h6>
206
+ <pre><code>SELECT
207
+ d.dept_name,
208
+ CONCAT(e.first_name, ' ', e.last_name) as manager_name,
209
+ dm.from_date as start_date,
210
+ CASE
211
+ WHEN dm.to_date = '9999-01-01' THEN 'Current'
212
+ ELSE dm.to_date
213
+ END as end_date
214
+ FROM
215
+ dept_manager dm
216
+ JOIN
217
+ departments d ON dm.dept_no = d.dept_no
218
+ JOIN
219
+ employees e ON dm.emp_no = e.emp_no
220
+ ORDER BY
221
+ d.dept_name, dm.from_date;</code></pre>
222
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT d.dept_name, CONCAT(e.first_name, \' \', e.last_name) as manager_name, dm.from_date as start_date, CASE WHEN dm.to_date = \'9999-01-01\' THEN \'Current\' ELSE dm.to_date END as end_date FROM dept_manager dm JOIN departments d ON dm.dept_no = d.dept_no JOIN employees e ON dm.emp_no = e.emp_no ORDER BY d.dept_name, dm.from_date;')">Копіювати до консолі</button>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <script>
229
+ function copyToQueryConsole(sql) {
230
+ // Передаємо SQL запит на головну сторінку
231
+ window.localStorage.setItem('query_to_execute', sql);
232
+ window.location.href = '/#sql-console';
233
+ }
234
+ </script>
235
+ </body>
236
+ </html>
templates/index.html CHANGED
@@ -34,21 +34,44 @@
34
  <div class="card-body">
35
  {% if status.status == 'online' %}
36
  <p><strong>Версія MySQL:</strong> {{ status.version }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  {% else %}
38
  <p class="text-danger"><strong>Помилка:</strong> {{ status.error }}</p>
39
  {% endif %}
40
  </div>
41
  </div>
42
 
43
- <!-- Завдання 1.2 (Імпорт бази Employees) -->
44
  <div class="card mb-4">
45
- <div class="card-header bg-primary text-white">
46
- <h5>Завдання 1.2 - Імпорт бази даних "Employees"</h5>
47
  </div>
48
  <div class="card-body">
49
- <p>Для виконання завдання 1.2 вам потрібно завантажити і імпортувати базу даних "Employees".</p>
50
- <a href="/download_employees" class="btn btn-success">Завантажити і імпортувати базу Employees</a>
51
- <p class="mt-3">Процес імпорту може зайняти кілька хвилин. Після успішного імпорту база даних "employees" з'явиться у списку доступних баз даних.</p>
 
 
 
 
 
 
 
 
52
  </div>
53
  </div>
54
 
@@ -59,53 +82,43 @@
59
  </div>
60
  <div class="card-body">
61
  <form action="/execute" method="post">
 
 
 
 
 
 
 
 
62
  <div class="mb-3">
63
  <label for="query" class="form-label">Введіть SQL запит:</label>
64
- <textarea class="form-control" id="query" name="query" rows="6" placeholder="SELECT * FROM test"></textarea>
65
  </div>
66
  <button type="submit" class="btn btn-primary">Виконати</button>
67
  </form>
68
  </div>
69
  </div>
70
 
71
- <!-- Список баз даних і таблиць -->
72
- <div class="row">
73
- <div class="col-md-6">
74
- <div class="card mb-4">
75
- <div class="card-header bg-secondary text-white">
76
- <h5>Бази даних</h5>
77
- </div>
78
- <div class="card-body">
79
- {% if databases %}
80
- <ul class="list-group">
81
- {% for db in databases %}
82
- <li class="list-group-item">{{ db }}</li>
83
- {% endfor %}
84
- </ul>
85
- {% else %}
86
- <p>Немає доступних баз даних</p>
87
- {% endif %}
88
- </div>
89
- </div>
90
  </div>
91
-
92
- <div class="col-md-6">
93
- <div class="card mb-4">
94
- <div class="card-header bg-secondary text-white">
95
- <h5>Таблиці в test_db</h5>
96
- </div>
97
- <div class="card-body">
98
- {% if tables %}
99
- <ul class="list-group">
100
- {% for table in tables %}
101
- <li class="list-group-item">{{ table }}</li>
102
- {% endfor %}
103
- </ul>
104
- {% else %}
105
- <p>Немає доступних таблиць</p>
106
- {% endif %}
107
  </div>
108
- </div>
 
 
109
  </div>
110
  </div>
111
 
@@ -127,6 +140,47 @@ CREATE TABLE IF NOT EXISTS test (
127
  <button class="btn btn-outline-primary" onclick="copyToQueryConsole('create table if not exists test (numbers int, words varchar (10));')">Копіювати до консолі</button>
128
  </div>
129
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
 
132
  <script>
 
34
  <div class="card-body">
35
  {% if status.status == 'online' %}
36
  <p><strong>Версія MySQL:</strong> {{ status.version }}</p>
37
+
38
+ {% if remote_connection %}
39
+ <div class="alert alert-info">
40
+ <h5>Підключено до віддаленої бази даних</h5>
41
+ <p>Ви підключені до віддаленого сервера MySQL з базою даних "Employees".</p>
42
+ </div>
43
+ {% endif %}
44
+
45
+ {% if employees_exists %}
46
+ <div class="alert alert-success">
47
+ <h5>База даних "Employees" доступна!</h5>
48
+ <p>Ви можете виконувати SQL-запити до бази даних employees.</p>
49
+ <a href="/employees" class="btn btn-sm btn-outline-success">Переглянути деталі</a>
50
+ </div>
51
+ {% endif %}
52
  {% else %}
53
  <p class="text-danger"><strong>Помилка:</strong> {{ status.error }}</p>
54
  {% endif %}
55
  </div>
56
  </div>
57
 
58
+ <!-- Параметри підключення -->
59
  <div class="card mb-4">
60
+ <div class="card-header bg-info text-white">
61
+ <h5>Параметри підключення</h5>
62
  </div>
63
  <div class="card-body">
64
+ <p>Для підключення до бази даних з локального комп'ютера використовуйте ці параметри:</p>
65
+ <ul>
66
+ <li><strong>Хост:</strong> {{ connection_info.host }}</li>
67
+ <li><strong>Порт:</strong> {{ connection_info.port }}</li>
68
+ <li><strong>Користувач:</strong> {{ connection_info.user }}</li>
69
+ <li><strong>Пароль:</strong> {{ connection_info.password }}</li>
70
+ <li><strong>SSL-режим:</strong> {{ connection_info.ssl_mode }}</li>
71
+ </ul>
72
+
73
+ <p>Команда для підключення через термінал:</p>
74
+ <pre><code>mysql -h {{ connection_info.host }} -P {{ connection_info.port }} -u {{ connection_info.user }} -p --ssl-mode=REQUIRED employees</code></pre>
75
  </div>
76
  </div>
77
 
 
82
  </div>
83
  <div class="card-body">
84
  <form action="/execute" method="post">
85
+ <div class="mb-3">
86
+ <label for="database" class="form-label">Виберіть базу даних:</label>
87
+ <select class="form-select" id="database" name="database">
88
+ {% for db in databases %}
89
+ <option value="{{ db }}" {% if db == 'employees' %}selected{% endif %}>{{ db }}</option>
90
+ {% endfor %}
91
+ </select>
92
+ </div>
93
  <div class="mb-3">
94
  <label for="query" class="form-label">Введіть SQL запит:</label>
95
+ <textarea class="form-control" id="query" name="query" rows="6" placeholder="SELECT * FROM employees LIMIT 10"></textarea>
96
  </div>
97
  <button type="submit" class="btn btn-primary">Виконати</button>
98
  </form>
99
  </div>
100
  </div>
101
 
102
+ <!-- Список баз даних -->
103
+ <div class="card mb-4">
104
+ <div class="card-header bg-secondary text-white">
105
+ <h5>Доступні бази даних</h5>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  </div>
107
+ <div class="card-body">
108
+ {% if databases %}
109
+ <div class="list-group">
110
+ {% for db in databases %}
111
+ <a href="/database/{{ db }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
112
+ {{ db }}
113
+ {% if db == 'employees' %}
114
+ <span class="badge bg-success">Навчальна база</span>
115
+ {% endif %}
116
+ </a>
117
+ {% endfor %}
 
 
 
 
 
118
  </div>
119
+ {% else %}
120
+ <p>Немає доступних баз даних або неможливо з'єднатися з сервером</p>
121
+ {% endif %}
122
  </div>
123
  </div>
124
 
 
140
  <button class="btn btn-outline-primary" onclick="copyToQueryConsole('create table if not exists test (numbers int, words varchar (10));')">Копіювати до консолі</button>
141
  </div>
142
  </div>
143
+
144
+ <!-- Приклади запитів -->
145
+ <div class="card mb-4">
146
+ <div class="card-header bg-success text-white">
147
+ <h5>Приклади SQL запитів до бази "Employees"</h5>
148
+ </div>
149
+ <div class="card-body">
150
+ <p>Спробуйте наступні запити:</p>
151
+
152
+ <div class="mb-3">
153
+ <h6>1. Перегляд структури таблиць</h6>
154
+ <pre><code>SHOW TABLES FROM employees;</code></pre>
155
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SHOW TABLES FROM employees;')">Копіювати до консолі</button>
156
+ </div>
157
+
158
+ <div class="mb-3">
159
+ <h6>2. Перегляд співробітників</h6>
160
+ <pre><code>SELECT emp_no, first_name, last_name, gender, hire_date
161
+ FROM employees
162
+ LIMIT 10;</code></pre>
163
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT emp_no, first_name, last_name, gender, hire_date FROM employees LIMIT 10;')">Копіювати до консолі</button>
164
+ </div>
165
+
166
+ <div class="mb-3">
167
+ <h6>3. Перегляд департаментів</h6>
168
+ <pre><code>SELECT * FROM departments;</code></pre>
169
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT * FROM departments;')">Копіювати до консолі</button>
170
+ </div>
171
+
172
+ <div class="mb-3">
173
+ <h6>4. Запит з JOIN</h6>
174
+ <pre><code>SELECT e.emp_no, e.first_name, e.last_name, d.dept_name
175
+ FROM employees e
176
+ JOIN dept_emp de ON e.emp_no = de.emp_no
177
+ JOIN departments d ON de.dept_no = d.dept_no
178
+ WHERE de.to_date = '9999-01-01'
179
+ LIMIT 10;</code></pre>
180
+ <button class="btn btn-sm btn-outline-primary" onclick="copyToQueryConsole('SELECT e.emp_no, e.first_name, e.last_name, d.dept_name FROM employees e JOIN dept_emp de ON e.emp_no = de.emp_no JOIN departments d ON de.dept_no = d.dept_no WHERE de.to_date = \'9999-01-01\' LIMIT 10;')">Копіювати до консолі</button>
181
+ </div>
182
+ </div>
183
+ </div>
184
  </div>
185
 
186
  <script>