This commit is contained in:
2025-07-26 04:41:47 +03:00
parent 019e4f90c7
commit f5cf4c32da
17 changed files with 2386 additions and 931 deletions

View File

@@ -1,24 +1,28 @@
<!DOCTYPE html>
<html lang="ru">
<html lang="{{ session.get('language', 'ru') }}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyHoreca OLAPer</title>
<title>{{ _('MyHoreca OLAPer') }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>MyHoreca OLAP-to-GoogleSheets</h1>
<h1>{{ _('MyHoreca OLAP-to-GoogleSheets') }}</h1>
{% if current_user.is_authenticated %}
<div class="user-info">
Logged in as: <strong>{{ current_user.username }}</strong> |
<a href="{{ url_for('logout') }}">Logout</a>
{{ _('Logged in as:') }} <strong>{{ current_user.username }}</strong> |
<a href="{{ url_for('.logout') }}">{{ _('Logout') }}</a>
<span class="lang-switcher">
<a href="{{ url_for('.set_language', language='ru') }}" title="{{ _('Русский') }}">🇷🇺</a> /
<a href="{{ url_for('.set_language', language='en') }}" title="{{ _('English') }}">🇬🇧</a>
</span>
</div>
{% else %}
<div class="user-info">
<a href="{{ url_for('login') }}">Login</a> |
<a href="{{ url_for('register') }}">Register</a>
<a href="{{ url_for('.login') }}">{{ _('Login') }}</a> |
<a href="{{ url_for('.register') }}">{{ _('Register') }}</a>
</div>
{% endif %}
@@ -34,105 +38,105 @@
{% if current_user.is_authenticated %}
<div class="container">
<!-- Секция RMS-сервера -->
<button type="button" class="collapsible">1. Connection to RMS-server</button>
<button type="button" class="collapsible">1. {{ _('Connection to RMS-server') }}</button>
<div class="content">
<h3>RMS Server Configuration</h3>
<h3>{{ _('RMS Server Configuration') }}</h3>
<p>
Enter the details for your RMS server API. This information is used to connect,
authenticate, and retrieve the list of available OLAP report presets.
{% trans %}Enter the details for your RMS server API. This information is used to connect,
authenticate, and retrieve the list of available OLAP report presets.{% endtrans %}
</p>
<form action="{{ url_for('configure_rms') }}" method="post">
<label for="host">RMS-host (e.g., http://your-rms-api.com/resto):</label>
<form action="{{ url_for('.configure_rms') }}" method="post">
<label for="host">{{ _('RMS-host (e.g., http://your-rms-api.com/resto):') }}</label>
<input type="text" id="host" name="host" value="{{ rms_config.get('host', '') }}" required /><br />
<label for="login">API Login:</label>
<label for="login">{{ _('API Login:') }}</label>
<input type="text" id="login" name="login" value="{{ rms_config.get('login', '') }}" required /><br />
<label for="password">API Password (enter if you want to change):</label>
<input type="password" id="password" name="password" value="" {% if not rms_config.get('password') %}required{% endif %} /><br />
{% if rms_config.get('password') %}
<small>Password is saved and will be used. Enter only if you need to change it.</small><br/>
<label for="password">{{ _('API Password:') }}</label>
<input type="password" id="password" name="password" value="" {% if not rms_config.password_is_set %}required{% endif %} /><br />
{% if rms_config.password_is_set %}
<small>{{ _('Password is saved. Enter a new one only if you need to change it.') }}</small><br/>
{% else %}
<small>Enter the API password for your RMS server.</small><br/>
<small>{{ _('Enter the API password for your RMS server.') }}</small><br/>
{% endif %}
<button type="submit">Check and Save RMS-config</button>
<button type="submit">{{ _('Check and Save RMS-config') }}</button>
</form>
{% if presets %}
<p><strong>Status:</strong> Successfully connected to RMS. Found {{ presets|length }} OLAP presets.</p>
<p><strong>{{ _('Status:') }}</strong> {% trans num=presets|length %}Successfully connected to RMS. Found %(num)s OLAP presets.{% endtrans %}</p>
{% elif rms_config.get('host') %}
<p><strong>Status:</strong> RMS configuration saved. Presets not yet loaded or connection failed.</p>
<p><strong>{{ _('Status:') }}</strong> {{ _('RMS configuration saved. Presets not yet loaded or connection failed.') }}</p>
{% endif %}
</div>
<!-- Секция Google-таблиц -->
<button type="button" class="collapsible" {% if not rms_config.get('host') %}disabled title="Configure RMS first"{% endif %}>
2. Google Sheets Configuration
<button type="button" class="collapsible" {% if not rms_config.get('host') %}disabled title="{{ _('Configure RMS first') }}"{% endif %}>
2. {{ _('Google Sheets Configuration') }}
</button>
<div class="content">
<h3>Google Sheets Configuration</h3>
<h3>{{ _('Google Sheets Configuration') }}</h3>
<p>
To allow the application to write to your Google Sheet, you need to provide
{% trans %}To allow the application to write to your Google Sheet, you need to provide
credentials for a Google Service Account. This account will act on behalf
of the application.
of the application.{% endtrans %}
</p>
<p>
<strong>How to get credentials:</strong>
<br>1. Go to Google Cloud Console.
<br>2. Create a new project or select an existing one.
<br>3. Enable the "Google Sheets API" and "Google Drive API" for the project.
<br>4. Go to "Credentials", click "Create Credentials", choose "Service Account".
<br>5. Give it a name, grant it necessary permissions (e.g., Editor role for simplicity, or more granular roles for Sheets/Drive).
<br>6. Create a JSON key for the service account. Download this file.
<br>7. Share your target Google Sheet with the service account's email address (found in the downloaded JSON file, key `client_email`).
<strong>{{ _('How to get credentials:') }}</strong>
<br>1. {{ _('Go to Google Cloud Console.') }}
<br>2. {{ _('Create a new project or select an existing one.') }}
<br>3. {{ _('Enable the "Google Sheets API" and "Google Drive API" for the project.') }}
<br>4. {{ _('Go to "Credentials", click "Create Credentials", choose "Service Account".') }}
<br>5. {{ _('Give it a name and grant it the "Editor" role.') }}
<br>6. {{ _('Create a JSON key for the service account and download the file.') }}
<br>7. {% trans %}Share your target Google Sheet with the service account's email address (found in the downloaded JSON file, key `client_email`).{% endtrans %}
</p>
<form action="{{ url_for('upload_credentials') }}" method="post" enctype="multipart/form-data">
<label for="cred_file">Service Account Credentials (JSON file):</label>
<input type="file" id="cred_file" name="cred_file" accept=".json" {% if not google_config.get('cred_file') %}required{% endif %} /><br />
<form action="{{ url_for('.upload_credentials') }}" method="post" enctype="multipart/form-data">
<label for="cred_file">{{ _('Service Account Credentials (JSON file):') }}</label>
<input type="file" id="cred_file" name="cred_file" accept=".json" /><br />
{% if client_email %}
<p><strong>Current Service Account Email:</strong> <code>{{ client_email }}</code></p>
<small>Upload a new file only if you need to change credentials.</small><br/>
<p><strong>{{ _('Current Service Account Email:') }}</strong> <code>{{ client_email }}</code></p>
<small>{{ _('Upload a new file only if you need to change credentials.') }}</small><br/>
{% else %}
<small>Upload the JSON file downloaded from Google Cloud Console.</small><br/>
<small>{{ _('Upload the JSON file downloaded from Google Cloud Console.') }}</small><br/>
{% endif %}
<button type="submit">Upload Credentials</button>
<button type="submit">{{ _('Upload Credentials') }}</button>
</form>
<hr>
<p>
Enter the URL of the Google Sheet you want to use. The service account email
(shown above after uploading credentials) must have edit access to this sheet.
{% trans %}Enter the URL of the Google Sheet you want to use. The service account email
(shown above after uploading credentials) must have edit access to this sheet.{% endtrans %}
</p>
<form action="{{ url_for('configure_google') }}" method="post">
<label for="sheet_url">Google Sheet URL:</label>
<form action="{{ url_for('.configure_google') }}" method="post">
<label for="sheet_url">{{ _('Google Sheet URL:') }}</label>
<input type="text" id="sheet_url" name="sheet_url" value="{{ google_config.get('sheet_url', '') }}" required placeholder="https://docs.google.com/spreadsheets/d/..."/>
<button type="submit" {% if not client_email %}disabled title="Upload Service Account Credentials first"{% endif %}>
Connect Google Sheets
<button type="submit" {% if not client_email %}disabled title="{{ _('Upload Service Account Credentials first') }}"{% endif %}>
{{ _('Connect Google Sheets') }}
</button>
{% if sheets %}
<p><strong>Status:</strong> Successfully connected to Google Sheet. Found {{ sheets|length }} worksheets.</p>
<p><strong>{{ _('Status:') }}</strong> {% trans num=sheets|length %}Successfully connected to Google Sheet. Found %(num)s worksheets.{% endtrans %}</p>
{% elif google_config.get('sheet_url') %}
<p><strong>Status:</strong> Google Sheet URL saved. Worksheets not yet loaded or connection failed.</p>
<p><strong>{{ _('Status:') }}</strong> {{ _('Google Sheet URL saved. Worksheets not yet loaded or connection failed.') }}</p>
{% endif %}
</form>
</div>
<!-- Секция сопоставления листов-отчетов -->
<button type="button" class="collapsible" {% if not sheets or not presets %}disabled title="Configure RMS and Google Sheets first"{% endif %}>
3. Mapping Sheets to OLAP Reports
<button type="button" class="collapsible" {% if not sheets or not presets %}disabled title="{{ _('Configure RMS and Google Sheets first') }}"{% endif %}>
3. {{ _('Mapping Sheets to OLAP Reports') }}
</button>
<div class="content">
<h3>Map Worksheets to OLAP Reports</h3>
<h3>{{ _('Map Worksheets to OLAP Reports') }}</h3>
<p>
Select which OLAP report from RMS should be rendered into each specific worksheet
(tab) in your Google Sheet.
{% trans %}Select which OLAP report from RMS should be rendered into each specific worksheet
(tab) in your Google Sheet.{% endtrans %}
</p>
{% if sheets and presets %}
<form action="{{ url_for('mapping_set') }}" method="post">
<form action="{{ url_for('.mapping_set') }}" method="post">
<table>
<thead>
<tr>
<th>Worksheet (Google Sheets)</th>
<th>OLAP-report (RMS)</th>
<th>{{ _('Worksheet (Google Sheets)') }}</th>
<th>{{ _('OLAP-report (RMS)') }}</th>
</tr>
</thead>
<tbody>
@@ -140,9 +144,8 @@
<tr>
<td>{{ sheet.title }}</td>
<td>
<!-- Use sheet.id for unique name -->
<select name="sheet_{{ sheet.id }}">
<option value="">-- Not set --</option>
<option value="">-- {{ _('Not set') }} --</option>
{% for preset in presets %}
<option value="{{ preset['id'] }}" {% if mappings.get(sheet.title) == preset['id'] %}selected{% endif %}>
{{ preset['name'] }} ({{ preset['id'] }})
@@ -154,58 +157,53 @@
{% endfor %}
</tbody>
</table>
<button type="submit">Save Mappings</button>
<button type="submit">{{ _('Save Mappings') }}</button>
</form>
{% elif not sheets and not presets %}
<p>Worksheets and OLAP presets are not loaded. Please configure RMS and Google Sheets first.</p>
<p>{{ _('Worksheets and OLAP presets are not loaded. Please configure RMS and Google Sheets first.') }}</p>
{% elif not sheets %}
<p>Worksheets are not loaded. Check Google Sheets configuration.</p>
<p>{{ _('Worksheets are not loaded. Check Google Sheets configuration.') }}</p>
{% elif not presets %}
<p>OLAP presets are not loaded. Check RMS configuration.</p>
<p>{{ _('OLAP presets are not loaded. Check RMS configuration.') }}</p>
{% endif %}
</div>
<!-- Секция отрисовки отчетов на листах -->
<button type="button" class="collapsible" {% if not mappings or mappings|length == 0 %}disabled title="Configure Mappings first"{% endif %}>
4. Render Reports to Sheets
<button type="button" class="collapsible" {% if not mappings or mappings|length == 0 %}disabled title="{{ _('Configure Mappings first') }}"{% endif %}>
4. {{ _('Render Reports to Sheets') }}
</button>
<div class="content">
<h3>Render Reports</h3>
<h3>{{ _('Render Reports') }}</h3>
<p>
Select the date range and click "Render to sheet" for each mapping you wish to execute.
{% trans %}Select the date range and click "Render to sheet" for each mapping you wish to execute.
The application will retrieve the OLAP data from RMS for the selected report and period,
clear the corresponding worksheet in Google Sheets, and write the new data.
clear the corresponding worksheet in Google Sheets, and write the new data.{% endtrans %}
</p>
{% if mappings and mappings|length > 0 %}
<form action="{{ url_for('render_olap') }}" method="post">
<label for="start_date">From Date:</label>
<form action="{{ url_for('.render_olap') }}" method="post">
<label for="start_date">{{ _('From Date:') }}</label>
<input type="date" id="start_date" name="start_date" required /><br />
<label for="end_date">To Date:</label>
<label for="end_date">{{ _('To Date:') }}</label>
<input type="date" id="end_date" name="end_date" required /><br />
<table>
<thead>
<tr>
<th>Worksheet</th>
<th>Mapped OLAP Report</th>
<th>Action</th>
<th>{{ _('Worksheet') }}</th>
<th>{{ _('Mapped OLAP Report') }}</th>
<th>{{ _('Action') }}</th>
</tr>
</thead>
<tbody>
{# Iterate through sheets loaded from Google, check for mapping #}
{% for sheet in sheets %}
{% set report_id = mappings.get(sheet.title) %}
{% if report_id %} {# Only display rows with a valid mapping #}
{# Find the preset name by ID using Jinja filters #}
{# Find the preset dictionary where 'id' attribute equals report_id #}
{% if report_id %}
{% set matching_presets = presets | selectattr('id', 'equalto', report_id) | list %}
{% set preset_name = 'ID: ' + report_id %} {# Default display if preset not found or unnamed #}
{# If a matching preset was found, get its name #}
{% set preset_name = _('ID: ') + report_id %}
{% if matching_presets %}
{% set preset = matching_presets[0] %}
{% set preset_name = preset.get('name', 'Unnamed Preset') %}
{% set preset_name = preset.get('name', _('Unnamed Preset')) %}
{% endif %}
<tr>
@@ -213,7 +211,7 @@
<td>{{ preset_name }}</td>
<td>
<button type="submit" name="render_{{ sheet.title }}">
Render to sheet
{{ _('Render to sheet') }}
</button>
</td>
</tr>
@@ -223,8 +221,8 @@
</table>
</form>
{% else %}
<p>No mappings configured yet.</p>
<p><small>Please go to the "Mapping Sheets to OLAP Reports" section (Step 3) to set up mappings.</small></p>
<p>{{ _('No mappings configured yet.') }}</p>
<p><small>{{ _('Please go to the "Mapping Sheets to OLAP Reports" section (Step 3) to set up mappings.') }}</small></p>
{% endif %}
</div>
@@ -235,56 +233,27 @@
var coll = document.getElementsByClassName("collapsible");
for (var i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function () {
// Не переключать, если кнопка отключена
if (this.disabled) return;
// Не выполнять действие, если кнопка отключена
if (this.disabled) {
return;
}
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
// Если max-height установлен (т.е. секция открыта), то скрыть ее
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.display = "block";
// Иначе (секция закрыта), установить max-height равным высоте контента
// Это "раскроет" секцию с плавной анимацией
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
// Optional: Auto-expand sections based on config state?
// This requires passing more state from the Flask app to the template.
// For now, keep it simple with manual expansion.
// window.addEventListener('load', () => {
// // Example logic: if RMS configured but Google not, open Google section
// const rmsConfigured = '{{ rms_config.get("host") }}' !== '';
// const googleCredsExist = '{{ client_email }}' !== '';
// const googleSheetUrlSet = '{{ google_config.get("sheet_url") }}' !== '';
// // Corrected lines:
// const presetsLoaded = {{ (presets|length > 0) | lower }};
// const sheetsLoaded = {{ (sheets|length > 0) | lower }};
// const mappingsExist = {{ (mappings|length > 0) | lower }};
// const collapsibles = document.getElementsByClassName("collapsible");
// if (rmsConfigured && !googleCredsExist) {
// // Find and click Google Sheets collapsible
// for (let i = 0; i < collapsibles.length; i++) {
// if (collapsibles[i].innerText.includes("Google Sheets Configuration")) {
// collapsibles[i].click();
// break;
// }
// }
// } else if (rmsConfigured && googleCredsExist && googleSheetUrlSet && presetsLoaded && sheetsLoaded && !mappingsExist) {
// // Find and click Mapping collapsible
// for (let i = 0; i in collapsibles.length; i++) { // <-- Potential typo here, should be <
// if (collapsibles[i].innerText.includes("Mapping Sheets to OLAP Reports")) {
// collapsibles[i].click();
// break;
// }
// }
// }
// // Add more conditions as needed
// });
</script>
{% else %}
<p style="text-align: center; margin-top: 50px;">Please, <a href="{{ url_for('login') }}">login</a> or <a href="{{ url_for('register') }}">register</a></p>
<p style="text-align: center; margin-top: 50px;">{{ _('Please,') }} <a href="{{ url_for('.login') }}">{{ _('login') }}</a> {{ _('or') }} <a href="{{ url_for('.register') }}">{{ _('register') }}</a></p>
{% endif %}
</body>
</html>

View File

@@ -1,12 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <!-- Link to your CSS -->
<title>{{ _('Login') }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="auth-container"> {# <-- Add this wrapper div #}
<h1>Login</h1>
<div class="auth-container">
<div class="lang-switcher-auth">
<a href="{{ url_for('.set_language', language='ru') }}">Русский</a> |
<a href="{{ url_for('.set_language', language='en') }}">English</a>
</div>
<h1>{{ _('Login') }}</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
@@ -15,17 +19,16 @@
{% endif %}
{% endwith %}
<form method="post">
<label for="username">Username:</label>
<label for="username">{{ _('Username:') }}</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<label for="password">{{ _('Password:') }}</label>
<input type="password" id="password" name="password" required><br>
{# Adjusted label and input for "Remember Me" #}
<label for="remember">
<input type="checkbox" name="remember" id="remember"> Remember Me
<input type="checkbox" name="remember" id="remember"> {{ _('Remember Me') }}
</label><br>
<button type="submit">Login</button>
<button type="submit">{{ _('Login') }}</button>
</form>
<p>Don't have an account? <a href="{{ url_for('register') }}">Register here</a></p>
</div> {# <-- Close the wrapper div #}
<p>{{ _("Don't have an account?") }} <a href="{{ url_for('.register') }}">{{ _('Register here') }}</a></p>
</div>
</body>
</html>

View File

@@ -1,12 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Register</title>
<title>{{ _('Register') }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <!-- Link to your CSS -->
</head>
<body>
<div class="auth-container"> {# <-- Add this wrapper div #}
<h1>Register</h1>
<div class="auth-container">
<div class="lang-switcher-auth">
<a href="{{ url_for('.set_language', language='ru') }}">Русский</a> |
<a href="{{ url_for('.set_language', language='en') }}">English</a>
</div>
<h1>{{ _('Register') }}</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
@@ -15,13 +19,13 @@
{% endif %}
{% endwith %}
<form method="post">
<label for="username">Username:</label>
<label for="username">{{ _('Username:') }}</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<label for="password">{{ _('Password:') }}</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Register</button>
<button type="submit">{{ _('Register') }}</button>
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login here</a></p>
</div> {# <-- Close the wrapper div #}
<p>{{ _("Already have an account?") }} <a href="{{ url_for('.login') }}">{{ _('Login here') }}</a></p>
</div>
</body>
</html>