Add source

This commit is contained in:
2025-11-11 09:44:40 +03:00
parent e5df885f14
commit b1e582530d
15 changed files with 1138 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package com.dsol.pki_management.app;
import javafx.application.Application;
/**
* Точка входа в приложение
*/
public class Launcher {
public static void main(String[] args) {
Application.launch(PKIApplication.class, args);
}
}

View File

@@ -0,0 +1,25 @@
package com.dsol.pki_management.app;
import com.dsol.pki_management.controllers.MainController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
/**
* Главный класс приложения PKI Management
*/
public class PKIApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(PKIApplication.class.getResource("/com/dsol/pki_management/views/main.fxml"));
fxmlLoader.setController(new MainController());
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("PKI Management");
stage.setScene(scene);
stage.show();
}
}

View File

@@ -0,0 +1,122 @@
package com.dsol.pki_management.components;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.util.List;
/**
* MenuItem - визуальный блок в меню.
* Не кликабельный, используется для группировки и визуального оформления подменю.
*/
public class MenuItem extends VBox {
@FXML
private Label titleLabel;
@FXML
private Separator separator;
private VBox subMenuContainer;
public MenuItem() {
loadFXML();
initializeSubMenuContainer();
}
/**
* Загружает FXML и применяет стили
*/
private void loadFXML() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/dsol/pki_management/components/menu-item.fxml"));
fxmlLoader.setController(this);
try {
VBox root = fxmlLoader.load();
this.setAlignment(root.getAlignment());
this.setSpacing(root.getSpacing());
this.setStyle(root.getStyle());
this.setPadding(root.getPadding());
this.getChildren().addAll(root.getChildren());
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
/**
* Инициализирует контейнер для SubMenuItem
*/
private void initializeSubMenuContainer() {
subMenuContainer = new VBox();
subMenuContainer.setSpacing(8.0);
subMenuContainer.setPadding(new Insets(8, 0, 0, 0));
getChildren().add(subMenuContainer);
}
public MenuItem(String title) {
this();
setTitle(title);
}
public void setTitle(String title) {
if (titleLabel != null) {
titleLabel.setText(title);
}
}
public String getTitle() {
return titleLabel != null ? titleLabel.getText() : "";
}
/**
* Добавляет SubMenuItem в этот блок меню
* Может принимать как один, так и несколько элементов
*/
public void addSubMenuItems(SubMenuItem... subMenuItems) {
if (subMenuItems.length > 0) {
subMenuContainer.getChildren().addAll(subMenuItems);
updateVisibility();
}
}
/**
* Удаляет SubMenuItem из этого блока меню
*/
public void removeSubMenuItem(SubMenuItem subMenuItem) {
subMenuContainer.getChildren().remove(subMenuItem);
}
/**
* Очищает все SubMenuItem из этого блока меню
*/
public void clearSubMenuItems() {
subMenuContainer.getChildren().clear();
}
/**
* Возвращает список всех SubMenuItem в этом блоке
*/
public List<SubMenuItem> getSubMenuItems() {
return subMenuContainer.getChildren().stream()
.filter(node -> node instanceof SubMenuItem)
.map(node -> (SubMenuItem) node)
.toList();
}
/**
* Обновляет видимость блока меню на основе видимости SubMenuItem
* Если все SubMenuItem скрыты, блок меню также скрывается
*/
public void updateVisibility() {
List<SubMenuItem> subMenuItems = getSubMenuItems();
boolean shouldBeVisible = subMenuItems.isEmpty() ||
subMenuItems.stream().anyMatch(SubMenuItem::isItemVisible);
this.setVisible(shouldBeVisible);
this.setManaged(shouldBeVisible);
}
}

View File

@@ -0,0 +1,81 @@
package com.dsol.pki_management.components;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import java.io.IOException;
import java.util.function.Consumer;
/**
* SubMenuItem - кликабельная ссылка для открытия функционала.
*/
public class SubMenuItem extends Button {
private Consumer<MouseEvent> onClickHandler;
private boolean itemVisible = true; // По умолчанию видимый
public SubMenuItem() {
loadFXML();
setOnAction(e -> {
if (onClickHandler != null) {
onClickHandler.accept(null);
}
});
}
public SubMenuItem(String text) {
this();
setText(text);
}
public SubMenuItem(String text, Consumer<MouseEvent> onClickHandler) {
this();
setText(text);
this.onClickHandler = onClickHandler;
}
/**
* Загружает FXML и применяет стили
*/
private void loadFXML() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/dsol/pki_management/components/sub-menu-item.fxml"));
fxmlLoader.setController(this);
try {
Button root = fxmlLoader.load();
this.setMnemonicParsing(root.isMnemonicParsing());
this.setMaxWidth(root.getMaxWidth());
this.setStyle(root.getStyle());
this.setText(root.getText());
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setOnClickHandler(Consumer<MouseEvent> handler) {
this.onClickHandler = handler;
}
public Consumer<MouseEvent> getOnClickHandler() {
return onClickHandler;
}
/**
* Устанавливает видимость элемента меню
* @param visible true - элемент видим, false - элемент скрыт
*/
public void setItemVisible(boolean visible) {
this.itemVisible = visible;
super.setVisible(visible);
super.setManaged(visible);
}
/**
* Проверяет, видим ли элемент меню
* @return true если элемент видим, false если скрыт
*/
public boolean isItemVisible() {
return itemVisible;
}
}

View File

@@ -0,0 +1,27 @@
package com.dsol.pki_management.config;
/**
* Константы и ключи для конфигурации приложения
*/
public class AppConfig {
// Ключи конфигурации
public static final String KEY_ADMIN_PASSWORD = "admin.password";
public static final String KEY_LANGUAGE = "app.language";
public static final String KEY_THEME = "app.theme";
public static final String KEY_WINDOW_WIDTH = "window.width";
public static final String KEY_WINDOW_HEIGHT = "window.height";
public static final String KEY_WINDOW_X = "window.x";
public static final String KEY_WINDOW_Y = "window.y";
// Значения по умолчанию
public static final String DEFAULT_ADMIN_PASSWORD = "admin123";
public static final String DEFAULT_LANGUAGE = "ru";
public static final String DEFAULT_THEME = "light";
public static final String DEFAULT_WINDOW_WIDTH = "960";
public static final String DEFAULT_WINDOW_HEIGHT = "640";
private AppConfig() {
// Утилитный класс, не должен быть инстанциирован
}
}

View File

@@ -0,0 +1,180 @@
package com.dsol.pki_management.config;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.logging.Logger;
/**
* Менеджер конфигурации приложения
* Хранит конфигурацию в скрытой папке пользователя
*/
public class ConfigManager {
private static final Logger logger = Logger.getLogger(ConfigManager.class.getName());
private static final String CONFIG_DIR_NAME = "pki_management";
private static final String CONFIG_FILE_NAME = "config.properties";
private static ConfigManager instance;
private final Path configDir;
private final Path configFile;
private final Properties properties;
private ConfigManager() {
String userHome = System.getProperty("user.home");
this.configDir = Paths.get(userHome, CONFIG_DIR_NAME);
this.configFile = configDir.resolve(CONFIG_FILE_NAME);
this.properties = new Properties();
initializeConfigDirectory();
loadConfig();
}
/**
* Получает единственный экземпляр ConfigManager
*/
public static synchronized ConfigManager getInstance() {
if (instance == null) {
instance = new ConfigManager();
}
return instance;
}
/**
* Инициализирует директорию конфигурации и делает её скрытой
*/
private void initializeConfigDirectory() {
try {
// Создаем директорию, если её нет
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
logger.info("Создана директория конфигурации: " + configDir);
}
// Делаем директорию скрытой
setHiddenAttribute(configDir, true);
// Создаем файл конфигурации, если его нет
if (!Files.exists(configFile)) {
Files.createFile(configFile);
logger.info("Создан файл конфигурации: " + configFile);
}
// Делаем файл скрытым
setHiddenAttribute(configFile, true);
} catch (IOException e) {
logger.severe("Ошибка при инициализации директории конфигурации: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Устанавливает атрибут скрытого файла/папки
* Для Windows использует DOS атрибут, для Unix-подобных систем - точку в начале имени
*/
private void setHiddenAttribute(Path path, boolean hidden) {
try {
if (isWindows()) {
// Для Windows используем DOS атрибут
Files.setAttribute(path, "dos:hidden", hidden);
logger.info("Атрибут 'hidden' установлен для: " + path);
} else {
// Для Unix-подобных систем файлы с точкой в начале автоматически скрыты
// Но так как пользователь указал конкретное имя, просто логируем
logger.info("В Unix-подобных системах файл будет виден. Для скрытия используйте имя, начинающееся с точки.");
}
} catch (IOException e) {
logger.warning("Не удалось установить атрибут скрытого файла/папки для " + path + ": " + e.getMessage());
} catch (UnsupportedOperationException e) {
logger.warning("Операция установки атрибута не поддерживается для " + path + ": " + e.getMessage());
}
}
/**
* Загружает конфигурацию из файла
*/
public void loadConfig() {
try (InputStream input = Files.newInputStream(configFile)) {
properties.load(input);
logger.info("Конфигурация загружена из файла");
} catch (FileNotFoundException e) {
logger.info("Файл конфигурации не найден, будет создан новый");
saveConfig(); // Создаем файл с дефолтными значениями
} catch (IOException e) {
logger.severe("Ошибка при загрузке конфигурации: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Сохраняет конфигурацию в файл
*/
public void saveConfig() {
try (OutputStream output = Files.newOutputStream(configFile)) {
properties.store(output, "PKI Management Configuration");
logger.info("Конфигурация сохранена в файл");
} catch (IOException e) {
logger.severe("Ошибка при сохранении конфигурации: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Получает значение свойства
*/
public String getProperty(String key) {
return properties.getProperty(key);
}
/**
* Получает значение свойства с дефолтным значением
*/
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
/**
* Устанавливает значение свойства
*/
public void setProperty(String key, String value) {
properties.setProperty(key, value);
}
/**
* Удаляет свойство
*/
public void removeProperty(String key) {
properties.remove(key);
}
/**
* Проверяет, существует ли свойство
*/
public boolean hasProperty(String key) {
return properties.containsKey(key);
}
/**
* Получает путь к директории конфигурации
*/
public Path getConfigDir() {
return configDir;
}
/**
* Получает путь к файлу конфигурации
*/
public Path getConfigFile() {
return configFile;
}
/**
* Проверяет, является ли операционная система Windows
*/
private boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
}

View File

@@ -0,0 +1,167 @@
package com.dsol.pki_management.controllers;
import com.dsol.pki_management.components.MenuItem;
import com.dsol.pki_management.components.SubMenuItem;
import com.dsol.pki_management.modules.test1.Test1Controller;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javafx.scene.input.MouseEvent;
/**
* Главный контроллер приложения
*/
public class MainController {
@FXML
private Button hamburgerButton;
@FXML
private VBox menuPane;
@FXML
private Pane menuOverlay;
@FXML
private VBox menuContent;
@FXML
private StackPane contentArea;
// Статический список всех SubMenuItem для доступа из других контроллеров
private static final List<SubMenuItem> allSubMenuItems = new ArrayList<>();
private static final List<MenuItem> allMenuItems = new ArrayList<>();
@FXML
public void initialize() {
hamburgerButton.setOnAction(e -> openMenu());
menuOverlay.setOnMouseClicked(e -> closeMenu());
setupMenuItems();
}
private void setupMenuItems() {
// Очищаем списки при повторной инициализации
allSubMenuItems.clear();
allMenuItems.clear();
// Определяем структуру меню
MenuItem mainFunctions = createMenuItem("Основные функции",
createSubMenuItem("Главная", e -> System.out.println("Открыта главная страница")),
createSubMenuItem("Мониторинг", e -> System.out.println("Открыт мониторинг"))
);
MenuItem management = createMenuItem("Управление",
createSubMenuItem("Сертификаты", e -> System.out.println("Открыто управление сертификатами")),
createSubMenuItem("Отчеты", e -> System.out.println("Открыты отчеты"))
);
MenuItem testMenu = createMenuItem("Test",
createSubMenuItem("Тест 1", e -> {
loadTest1Module();
closeMenu();
})
);
// Добавляем блоки меню в контент меню
if (menuContent != null) {
menuContent.getChildren().addAll(mainFunctions, management, testMenu);
}
}
/**
* Создает MenuItem с указанным названием и подменю
*/
private MenuItem createMenuItem(String title, SubMenuItem... subMenuItems) {
MenuItem menuItem = new MenuItem(title);
allMenuItems.add(menuItem);
if (subMenuItems.length > 0) {
menuItem.addSubMenuItems(subMenuItems);
for (SubMenuItem subMenuItem : subMenuItems) {
allSubMenuItems.add(subMenuItem);
}
}
return menuItem;
}
/**
* Создает SubMenuItem с указанным текстом и обработчиком
*/
private SubMenuItem createSubMenuItem(String text, Consumer<MouseEvent> onClickHandler) {
return new SubMenuItem(text, onClickHandler);
}
/**
* Возвращает список всех SubMenuItem для доступа из других контроллеров
*/
public static List<SubMenuItem> getAllSubMenuItems() {
return new ArrayList<>(allSubMenuItems);
}
/**
* Возвращает список всех MenuItem для доступа из других контроллеров
*/
public static List<MenuItem> getAllMenuItems() {
return new ArrayList<>(allMenuItems);
}
@FXML
private void closeMenu() {
menuPane.setVisible(false);
menuPane.setManaged(false);
menuOverlay.setVisible(false);
menuOverlay.setManaged(false);
}
private void openMenu() {
menuPane.setVisible(true);
menuPane.setManaged(true);
menuOverlay.setVisible(true);
menuOverlay.setManaged(true);
}
/**
* Загружает модуль "Тест 1" в центральную область
*/
private void loadTest1Module() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/dsol/pki_management/modules/test1/test1-view.fxml"));
VBox test1View = loader.load();
if (contentArea != null) {
contentArea.getChildren().clear();
contentArea.getChildren().add(test1View);
}
} catch (IOException e) {
System.err.println("Ошибка загрузки модуля Тест 1: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Загружает модуль настроек в центральную область
*/
@FXML
private void loadSettingsModule() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/dsol/pki_management/modules/settings/settings-view.fxml"));
VBox settingsView = loader.load();
if (contentArea != null) {
contentArea.getChildren().clear();
contentArea.getChildren().add(settingsView);
}
closeMenu();
} catch (IOException e) {
System.err.println("Ошибка загрузки модуля настроек: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,241 @@
package com.dsol.pki_management.controllers;
import com.dsol.pki_management.components.MenuItem;
import com.dsol.pki_management.components.SubMenuItem;
import com.dsol.pki_management.config.AppConfig;
import com.dsol.pki_management.config.ConfigManager;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Modality;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Контроллер для модуля настроек
*/
public class SettingsController {
@FXML
private VBox userSettingsContainer;
@FXML
private VBox adminSettingsContainer;
@FXML
private Button adminAccessButton;
@FXML
private Button exitAdminButton;
@FXML
private Label statusLabel;
@FXML
private VBox subMenuItemsContainer;
private boolean isAdminMode = false;
private final ConfigManager configManager = ConfigManager.getInstance();
private Map<SubMenuItem, Text> subMenuItemTexts = new HashMap<>();
// Константы стилей
private static final String STYLE_MENU_ITEM_CONTAINER = "-fx-background-color: #e9ecef; -fx-background-radius: 4;";
private static final String STYLE_MENU_ITEM_LABEL = "-fx-font-size: 16px; -fx-font-weight: bold; -fx-text-fill: #212529;";
private static final String STYLE_SUB_ITEM_VISIBLE = "-fx-font-size: 14px; -fx-fill: #495057; -fx-cursor: hand;";
private static final String STYLE_SUB_ITEM_HIDDEN = "-fx-font-size: 14px; -fx-fill: #dc3545; -fx-cursor: hand;";
@FXML
public void initialize() {
setContainerVisibility(userSettingsContainer, true);
setContainerVisibility(adminSettingsContainer, false);
if (adminAccessButton != null) {
adminAccessButton.setOnAction(e -> showAdminPasswordDialog());
}
if (exitAdminButton != null) {
exitAdminButton.setOnAction(e -> disableAdminMode());
}
}
/**
* Устанавливает видимость контейнера
*/
private void setContainerVisibility(VBox container, boolean visible) {
container.setVisible(visible);
container.setManaged(visible);
}
/**
* Показывает диалог для ввода пароля администратора
*/
private void showAdminPasswordDialog() {
PasswordField passwordField = new PasswordField();
passwordField.setPromptText("Пароль");
VBox content = new VBox(10);
content.getChildren().addAll(new Label("Пароль:"), passwordField);
content.setPadding(new Insets(20));
Dialog<String> dialog = new Dialog<>();
dialog.setTitle("Админский доступ");
dialog.setHeaderText("Введите пароль администратора");
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.getDialogPane().setContent(content);
ButtonType loginButtonType = new ButtonType("Войти", ButtonBar.ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(loginButtonType, ButtonType.CANCEL);
((Button) dialog.getDialogPane().lookupButton(loginButtonType)).setDefaultButton(true);
dialog.setResultConverter(button -> button == loginButtonType ? passwordField.getText() : null);
dialog.setOnShown(e -> Platform.runLater(passwordField::requestFocus));
dialog.showAndWait().ifPresent(password -> {
String adminPassword = configManager.getProperty(AppConfig.KEY_ADMIN_PASSWORD, AppConfig.DEFAULT_ADMIN_PASSWORD);
if (adminPassword.equals(password)) {
enableAdminMode();
} else {
showErrorDialog("Неверный пароль", "Введен неверный пароль. Доступ запрещен.");
}
});
}
/**
* Включает режим администратора
*/
private void enableAdminMode() {
isAdminMode = true;
setContainerVisibility(userSettingsContainer, false);
setContainerVisibility(adminSettingsContainer, true);
if (statusLabel != null) {
statusLabel.setText("Режим администратора активен");
statusLabel.setStyle("-fx-text-fill: green;");
}
loadSubMenuItemsControls();
}
/**
* Загружает дерево для управления видимостью SubMenuItem
*/
private void loadSubMenuItemsControls() {
if (subMenuItemsContainer == null) {
return;
}
// Очищаем контейнер
subMenuItemsContainer.getChildren().clear();
subMenuItemTexts.clear();
// Добавляем отступ для всего блока
subMenuItemsContainer.setPadding(new Insets(15, 0, 15, 0));
subMenuItemsContainer.setSpacing(8);
// Получаем все MenuItem и SubMenuItem из HelloController
List<MenuItem> menuItems = MainController.getAllMenuItems();
if (menuItems.isEmpty()) {
Label noItemsLabel = new Label("Нет элементов меню для управления");
noItemsLabel.setStyle("-fx-text-fill: #6c757d;");
subMenuItemsContainer.getChildren().add(noItemsLabel);
return;
}
// Создаем дерево: для каждого MenuItem показываем заголовок и его SubMenuItem
for (MenuItem menuItem : menuItems) {
subMenuItemsContainer.getChildren().add(createMenuItemHeader(menuItem.getTitle()));
for (SubMenuItem subMenuItem : menuItem.getSubMenuItems()) {
Text subItemText = createSubMenuItemText(subMenuItem);
subMenuItemTexts.put(subMenuItem, subItemText);
subMenuItemsContainer.getChildren().add(createSubMenuItemContainer(subItemText));
}
}
}
/**
* Создает заголовок MenuItem
*/
private HBox createMenuItemHeader(String title) {
HBox container = new HBox();
container.setPadding(new Insets(8, 12, 8, 12));
container.setStyle(STYLE_MENU_ITEM_CONTAINER);
Label label = new Label("📁 " + title);
label.setStyle(STYLE_MENU_ITEM_LABEL);
container.getChildren().add(label);
return container;
}
/**
* Создает контейнер для SubMenuItem с отступом
*/
private HBox createSubMenuItemContainer(Text text) {
HBox container = new HBox();
container.setPadding(new Insets(4, 0, 4, 30));
container.getChildren().add(text);
return container;
}
/**
* Создает кликабельный Text для SubMenuItem
*/
private Text createSubMenuItemText(SubMenuItem subMenuItem) {
Text text = new Text("" + subMenuItem.getText());
// Устанавливаем стиль в зависимости от видимости
updateSubMenuItemTextStyle(text, subMenuItem.isItemVisible());
// Делаем Text кликабельным
text.setStyle(text.getStyle() + " -fx-cursor: hand;");
text.setOnMouseClicked(e -> toggleSubMenuItemVisibility(subMenuItem, text));
return text;
}
/**
* Переключает видимость SubMenuItem при клике
*/
private void toggleSubMenuItemVisibility(SubMenuItem subMenuItem, Text text) {
boolean newVisibility = !subMenuItem.isItemVisible();
subMenuItem.setItemVisible(newVisibility);
updateSubMenuItemTextStyle(text, newVisibility);
// Обновляем видимость всех MenuItem
MainController.getAllMenuItems().forEach(MenuItem::updateVisibility);
}
/**
* Обновляет стиль Text в зависимости от видимости
*/
private void updateSubMenuItemTextStyle(Text text, boolean isVisible) {
text.setStyle(isVisible ? STYLE_SUB_ITEM_VISIBLE : STYLE_SUB_ITEM_HIDDEN);
text.setStrikethrough(!isVisible);
}
/**
* Отключает режим администратора
*/
@FXML
private void disableAdminMode() {
isAdminMode = false;
setContainerVisibility(userSettingsContainer, true);
setContainerVisibility(adminSettingsContainer, false);
if (statusLabel != null) {
statusLabel.setText("");
}
}
/**
* Показывает диалог с ошибкой
*/
private void showErrorDialog(String title, String message) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
}
}

View File

@@ -0,0 +1,23 @@
package com.dsol.pki_management.modules.test1;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
/**
* Контроллер для модуля "Тест 1"
*/
public class Test1Controller {
@FXML
private VBox root;
@FXML
private Label titleLabel;
@FXML
public void initialize() {
if (titleLabel != null) {
titleLabel.setText("Модуль Тест 1");
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
alignment="TOP_LEFT"
spacing="8.0"
style="-fx-padding: 8 0 8 0;">
<children>
<Label fx:id="titleLabel"
style="-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #495057;"
text="Название блока"/>
<Separator fx:id="separator"
prefWidth="150.0"
style="-fx-padding: 0 0 4 0;"/>
</children>
</VBox>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<Button xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
mnemonicParsing="false"
maxWidth="Infinity"
style="-fx-background-color: transparent; -fx-text-fill: #0d6efd; -fx-alignment: CENTER_LEFT; -fx-cursor: hand;"/>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.VBox?>
<VBox fx:controller="com.dsol.pki_management.controllers.SettingsController"
xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
alignment="TOP_LEFT"
spacing="20.0"
style="-fx-background-color: white; -fx-padding: 40;">
<children>
<Label style="-fx-font-size: 24px; -fx-font-weight: bold; -fx-text-fill: #0d6efd;"
text="Настройки"/>
<Separator/>
<!-- Пользовательские настройки -->
<VBox fx:id="userSettingsContainer" spacing="15.0">
<children>
<Label style="-fx-font-size: 18px; -fx-font-weight: bold;"
text="Пользовательские настройки"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Язык интерфейса"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Тема оформления"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Уведомления"/>
<Separator/>
<Button fx:id="adminAccessButton"
mnemonicParsing="false"
style="-fx-background-color: #0d6efd; -fx-text-fill: white; -fx-padding: 10 20 10 20;"
text="Админский доступ"/>
</children>
</VBox>
<!-- Админские настройки -->
<VBox fx:id="adminSettingsContainer" spacing="15.0" visible="false" managed="false">
<children>
<Label style="-fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: #dc3545;"
text="Админские настройки"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Управление пользователями"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Системные параметры"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Логи и аудит"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Резервное копирование"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #495057;"
text="• Безопасность"/>
<Separator/>
<Label style="-fx-font-size: 16px; -fx-font-weight: bold; -fx-text-fill: #495057;"
text="Управление видимостью элементов меню"/>
<VBox fx:id="subMenuItemsContainer" spacing="8.0">
<!-- Дерево MenuItem и SubMenuItem будет добавлено программно -->
</VBox>
<Separator/>
<Button fx:id="exitAdminButton"
mnemonicParsing="false"
style="-fx-background-color: #dc3545; -fx-text-fill: white; -fx-padding: 10 20 10 20;"
text="Выйти из админ режима"/>
</children>
</VBox>
<Label fx:id="statusLabel" style="-fx-font-size: 12px;"/>
</children>
</VBox>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox fx:controller="com.dsol.pki_management.modules.test1.Test1Controller"
xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
alignment="CENTER"
spacing="20.0"
style="-fx-background-color: white; -fx-padding: 40;">
<children>
<Label fx:id="titleLabel"
style="-fx-font-size: 24px; -fx-font-weight: bold; -fx-text-fill: #0d6efd;"
text="Модуль Тест 1"/>
<Label style="-fx-font-size: 16px; -fx-text-fill: #495057;"
text="Это функциональный модуль для тестирования"/>
<Label style="-fx-font-size: 14px; -fx-text-fill: #6c757d;"
text="Здесь можно добавить любой функционал"/>
</children>
</VBox>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<StackPane prefWidth="960.0" prefHeight="640.0"
xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<BorderPane prefWidth="960.0" prefHeight="640.0">
<top>
<VBox style="-fx-background-color: #f8f9fa;">
<children>
<HBox alignment="CENTER_LEFT" spacing="16.0" style="-fx-padding: 12 16 12 16;">
<children>
<Button fx:id="hamburgerButton"
mnemonicParsing="false"
style="-fx-background-color: transparent; -fx-border-color: #0d6efd; -fx-border-radius: 6; -fx-text-fill: #0d6efd;"
text="Меню"/>
<Label style="-fx-font-size: 20px; -fx-font-weight: bold;"
text="PKI Management"/>
</children>
</HBox>
</children>
</VBox>
</top>
<center>
<StackPane fx:id="contentArea" style="-fx-background-color: white;">
<children>
<Label style="-fx-font-size: 18px;" text="Основное содержимое приложения"/>
</children>
</StackPane>
</center>
</BorderPane>
<Pane fx:id="menuOverlay"
visible="false" managed="false"
style="-fx-background-color: rgba(0, 0, 0, 0.45);"/>
<VBox fx:id="menuPane"
alignment="TOP_LEFT"
prefWidth="200.0"
maxWidth="200.0"
minWidth="200.0"
StackPane.alignment="CENTER_LEFT"
style="-fx-background-color: #ffffff; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 15, 0, 2, 0);"
visible="false" managed="false">
<padding>
<Insets top="24.0" right="24.0" bottom="24.0" left="24.0"/>
</padding>
<children>
<Label style="-fx-font-size: 22px; -fx-font-weight: bold;" text="Навигация"/>
<Separator prefWidth="150.0"/>
<VBox fx:id="menuContent" spacing="12.0" VBox.vgrow="ALWAYS">
<children>
<!-- Компоненты MenuItem и SubMenuItem будут добавлены программно -->
</children>
</VBox>
<Region VBox.vgrow="ALWAYS"/>
<Separator prefWidth="150.0"/>
<Hyperlink onAction="#closeMenu"
style="-fx-text-fill: #6c757d; -fx-alignment: CENTER_LEFT;"
text="Закрыть меню"/>
<Hyperlink onAction="#loadSettingsModule"
style="-fx-text-fill: #6c757d; -fx-alignment: CENTER_LEFT;"
text="⚙️ Настройки"/>
</children>
</VBox>
</children>
</StackPane>

47
ui/main.html Normal file
View File

@@ -0,0 +1,47 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hamburger меню слева</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- Кнопка гамбургера -->
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<button class="btn btn-outline-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasMenu" aria-controls="offcanvasMenu">
<span class="navbar-toggler-icon"></span> Меню
</button>
<a class="navbar-brand ms-3" href="#">Мой сайт</a>
</div>
</nav>
<!-- Offcanvas меню -->
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasMenu" aria-labelledby="offcanvasMenuLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasMenuLabel">Навигация</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
</div>
<!-- Тело меню с разделением на верхнюю и нижнюю часть -->
<div class="offcanvas-body d-flex flex-column justify-content-between">
<!-- Верхняя часть -->
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Главная</a>
</li>
</ul>
<!-- Нижняя часть -->
<div class="border-top pt-3">
<a class="nav-link text-secondary" href="#">
⚙️ Настройки
</a>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>