have done setting page

This commit is contained in:
2026-07-02 15:46:35 +07:00
parent 4793dc9c17
commit 2181547184
16 changed files with 1019 additions and 1 deletions
+3 -1
View File
@@ -20,7 +20,7 @@ Customize Chỉnh sửa giao diện trực quan theo thời gian thực (Header,
Widgets Quản lý các khối nội dung ở Sidebar hoặc Footer.
Menus Thiết kế menu điều hướng chính ở đầu trang và chân trang.
Theme File Editor Chỉnh sửa trực tiếp file code của giao diện (chỉ dùng cho lập trình viên).
7. Plugins (Gói mở rộng) Installed Plugins Quản lý, kích hoạt, vô hiệu hóa hoặc xóa các Plugin đang có trên hệ thống.
(done)7. Plugins (Gói mở rộng) Installed Plugins Quản lý, kích hoạt, vô hiệu hóa hoặc xóa các Plugin đang có trên hệ thống.
Add New Tìm kiếm và cài đặt Plugin mới từ thư viện WordPress.org hoặc upload file .zip.
Plugin File Editor Chỉnh sửa mã nguồn của các Plugin trực tiếp từ admin.
8. Users (Thành viên) All Users Quản lý danh sách toàn bộ tài khoản đăng ký trên hệ thống.
@@ -37,6 +37,8 @@ Discussion Cấu hình bình luận (yêu cầu phê duyệt, chặn từ ngữ
Media Cài đặt kích thước mặc định cho ảnh khi upload (Thumbnail, Medium, Large).
Permalinks Định dạng cấu trúc đường dẫn tĩnh (URL) tối ưu cho SEO (ví dụ: /post-name/).
Privacy Thiết lập trang chính sách bảo mật cho website.
PHẦN 2: CÁC HÀM PHP & HOOKS ĐỂ PHÁT TRIỂN TRANG ADMIN (DEVELOPER API)
Nếu bạn đang lập trình tùy biến trang admin (hoặc xây dựng CMS tương tự), WordPress cung cấp bộ API để tạo và kiểm soát trang admin sau:
@@ -44,6 +44,10 @@ public class CacheConfiguration {
createCache(cm, com.sisvietnamvn.web.domain.User.class.getName());
createCache(cm, com.sisvietnamvn.web.domain.Authority.class.getName());
createCache(cm, com.sisvietnamvn.web.domain.User.class.getName() + ".authorities");
createCache(cm, com.sisvietnamvn.web.domain.Media.class.getName());
createCache(cm, com.sisvietnamvn.web.domain.Plugin.class.getName());
createCache(cm, com.sisvietnamvn.web.domain.Setting.class.getName());
createCache(cm, "settings");
// jhipster-needle-ehcache-add-entry
};
}
@@ -0,0 +1,142 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.service.SettingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Map;
@Controller
@RequestMapping("/manage/settings")
public class ManageSettingController {
private static final Logger LOG = LoggerFactory.getLogger(ManageSettingController.class);
private final SettingService settingService;
public ManageSettingController(SettingService settingService) {
this.settingService = settingService;
}
private void addSettingsToModel(Model model) {
model.addAttribute("settings", settingService.getAllAsMap());
}
private String saveSettings(Map<String, String> allParams, RedirectAttributes redirectAttributes, String viewName) {
// Remove CSRF token from params before saving
allParams.remove("_csrf");
try {
settingService.setValues(allParams);
redirectAttributes.addFlashAttribute("successMessage", "Settings saved successfully.");
} catch (Exception e) {
LOG.error("Error saving settings", e);
redirectAttributes.addFlashAttribute("errorMessage", "Failed to save settings: " + e.getMessage());
}
return "redirect:/manage/settings/" + viewName;
}
// --- General Settings ---
@GetMapping("/general")
public String showGeneralSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/general";
}
@PostMapping("/general/save")
public String saveGeneralSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
return saveSettings(allParams, redirectAttributes, "general");
}
// --- Writing Settings ---
@GetMapping("/writing")
public String showWritingSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/writing";
}
@PostMapping("/writing/save")
public String saveWritingSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
return saveSettings(allParams, redirectAttributes, "writing");
}
// --- Reading Settings ---
@GetMapping("/reading")
public String showReadingSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/reading";
}
@PostMapping("/reading/save")
public String saveReadingSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
// Handle un-checked checkboxes which don't send a value
if (!allParams.containsKey("search_engine_visibility")) {
allParams.put("search_engine_visibility", "0");
}
return saveSettings(allParams, redirectAttributes, "reading");
}
// --- Discussion Settings ---
@GetMapping("/discussion")
public String showDiscussionSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/discussion";
}
@PostMapping("/discussion/save")
public String saveDiscussionSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
// Handle checkboxes
if (!allParams.containsKey("allow_comments")) allParams.put("allow_comments", "0");
if (!allParams.containsKey("comment_author_must_fill")) allParams.put("comment_author_must_fill", "0");
if (!allParams.containsKey("comment_manual_approval")) allParams.put("comment_manual_approval", "0");
return saveSettings(allParams, redirectAttributes, "discussion");
}
// --- Media Settings ---
@GetMapping("/media")
public String showMediaSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/media";
}
@PostMapping("/media/save")
public String saveMediaSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
return saveSettings(allParams, redirectAttributes, "media");
}
// --- Permalinks Settings ---
@GetMapping("/permalinks")
public String showPermalinksSettings(Model model) {
addSettingsToModel(model);
return "manage/settings/permalinks";
}
@PostMapping("/permalinks/save")
public String savePermalinksSettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
return saveSettings(allParams, redirectAttributes, "permalinks");
}
// --- Privacy Settings ---
@GetMapping("/privacy")
public String showPrivacySettings(Model model) {
addSettingsToModel(model);
return "manage/settings/privacy";
}
@PostMapping("/privacy/save")
public String savePrivacySettings(@RequestParam Map<String, String> allParams, RedirectAttributes redirectAttributes) {
return saveSettings(allParams, redirectAttributes, "privacy");
}
}
@@ -0,0 +1,104 @@
package com.sisvietnamvn.web.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import java.io.Serializable;
/**
* A Setting.
* This entity stores key-value configuration pairs for the application.
*/
@Entity
@Table(name = "sis_setting")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Setting extends AbstractAuditingEntity<Long> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator", sequenceName = "sis_setting_seq", allocationSize = 1)
private Long id;
@NotNull
@Size(max = 100)
@Column(name = "setting_key", length = 100, nullable = false, unique = true)
private String settingKey;
@Size(max = 2000)
@Column(name = "setting_value", length = 2000)
private String settingValue;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Setting id(Long id) {
this.setId(id);
return this;
}
public String getSettingKey() {
return settingKey;
}
public void setSettingKey(String settingKey) {
this.settingKey = settingKey;
}
public Setting settingKey(String settingKey) {
this.setSettingKey(settingKey);
return this;
}
public String getSettingValue() {
return settingValue;
}
public void setSettingValue(String settingValue) {
this.settingValue = settingValue;
}
public Setting settingValue(String settingValue) {
this.setSettingValue(settingValue);
return this;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Setting)) {
return false;
}
return id != null && id.equals(((Setting) o).id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "Setting{" +
"id=" + getId() +
", settingKey='" + getSettingKey() + "'" +
", settingValue='" + getSettingValue() + "'" +
"}";
}
}
@@ -0,0 +1,15 @@
package com.sisvietnamvn.web.repository;
import com.sisvietnamvn.web.domain.Setting;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* Spring Data JPA repository for the Setting entity.
*/
@Repository
public interface SettingRepository extends JpaRepository<Setting, Long> {
Optional<Setting> findBySettingKey(String settingKey);
}
@@ -0,0 +1,79 @@
package com.sisvietnamvn.web.service;
import com.sisvietnamvn.web.domain.Setting;
import com.sisvietnamvn.web.repository.SettingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Transactional
public class SettingService {
private static final Logger LOG = LoggerFactory.getLogger(SettingService.class);
private final SettingRepository settingRepository;
public SettingService(SettingRepository settingRepository) {
this.settingRepository = settingRepository;
}
/**
* Get a setting value by key.
*/
@Transactional(readOnly = true)
@Cacheable(value = "settings", key = "#key")
public String getValue(String key, String defaultValue) {
return settingRepository.findBySettingKey(key)
.map(Setting::getSettingValue)
.orElse(defaultValue);
}
/**
* Set a setting value by key.
*/
@CacheEvict(value = "settings", allEntries = true)
public void setValue(String key, String value) {
Optional<Setting> settingOpt = settingRepository.findBySettingKey(key);
if (settingOpt.isPresent()) {
Setting setting = settingOpt.get();
setting.setSettingValue(value);
settingRepository.save(setting);
LOG.debug("Updated setting '{}' to '{}'", key, value);
} else {
Setting setting = new Setting();
setting.setSettingKey(key);
setting.setSettingValue(value);
settingRepository.save(setting);
LOG.debug("Created new setting '{}' with value '{}'", key, value);
}
}
/**
* Set multiple settings at once.
*/
@CacheEvict(value = "settings", allEntries = true)
public void setValues(Map<String, String> settings) {
settings.forEach(this::setValue);
}
/**
* Get all settings as a map for easy rendering in UI templates.
*/
@Transactional(readOnly = true)
public Map<String, String> getAllAsMap() {
List<Setting> allSettings = settingRepository.findAll();
return allSettings.stream()
.collect(Collectors.toMap(Setting::getSettingKey, setting ->
setting.getSettingValue() == null ? "" : setting.getSettingValue()
));
}
}
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity Setting.
-->
<changeSet id="20260702153000-1" author="antigravity">
<createTable tableName="sis_setting">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="setting_key" type="varchar(100)">
<constraints nullable="false" unique="true" uniqueConstraintName="ux_sis_setting__key"/>
</column>
<column name="setting_value" type="varchar(2000)">
<constraints nullable="true"/>
</column>
<!-- Audit fields -->
<column name="created_by" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="created_date" type="timestamp"/>
<column name="last_modified_by" type="varchar(50)"/>
<column name="last_modified_date" type="timestamp"/>
</createTable>
<createSequence sequenceName="sis_setting_seq" startValue="1" incrementBy="1"/>
<!-- Seed default settings -->
<!-- General -->
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="site_name"/>
<column name="setting_value" value="My Awesome Website"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="tagline"/>
<column name="setting_value" value="Just another Spring Boot site"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="admin_email"/>
<column name="setting_value" value="admin@example.com"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="timezone"/>
<column name="setting_value" value="UTC"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="date_format"/>
<column name="setting_value" value="yyyy-MM-dd"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<!-- Writing -->
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="default_category"/>
<column name="setting_value" value="1"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<!-- Reading -->
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="posts_per_page"/>
<column name="setting_value" value="10"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
<insert tableName="sis_setting">
<column name="id" valueSequenceNext="sis_setting_seq"/>
<column name="setting_key" value="search_engine_visibility"/>
<column name="setting_value" value="1"/>
<column name="created_by" value="system"/>
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -28,6 +28,7 @@
<include file="config/liquibase/changelog/20260629165300_add_totp_to_user.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20260702143000_add_media_entity.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20260702144700_add_plugin_entity.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20260702153000_add_setting_entity.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
@@ -170,6 +170,27 @@
</div>
</li>
<!-- Nav Item - Settings Collapse Menu -->
<li class="nav-item">
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseSettings"
aria-expanded="true" aria-controls="collapseSettings">
<i class="fas fa-fw fa-cogs"></i>
<span>Settings</span>
</a>
<div id="collapseSettings" class="collapse" aria-labelledby="headingSettings" data-parent="#accordionSidebar">
<div class="bg-white py-2 collapse-inner rounded">
<h6 class="collapse-header">Configuration:</h6>
<a class="collapse-item" th:href="@{/manage/settings/general}">General</a>
<a class="collapse-item" th:href="@{/manage/settings/writing}">Writing</a>
<a class="collapse-item" th:href="@{/manage/settings/reading}">Reading</a>
<a class="collapse-item" th:href="@{/manage/settings/discussion}">Discussion</a>
<a class="collapse-item" th:href="@{/manage/settings/media}">Media</a>
<a class="collapse-item" th:href="@{/manage/settings/permalinks}">Permalinks</a>
<a class="collapse-item" th:href="@{/manage/settings/privacy}">Privacy</a>
</div>
</div>
</li>
<!-- Divider -->
<hr class="sidebar-divider d-none d-md-block">
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Discussion Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Discussion Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/discussion/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Default post settings</label>
<div class="col-sm-9 mt-2">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="allow_comments" value="1" th:checked="${settings['allow_comments'] == '1'}">
<label class="form-check-label">Allow people to submit comments on new posts</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Other comment settings</label>
<div class="col-sm-9 mt-2">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="comment_author_must_fill" value="1" th:checked="${settings['comment_author_must_fill'] == '1'}">
<label class="form-check-label">Comment author must fill out name and email</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Before a comment appears</label>
<div class="col-sm-9 mt-2">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="comment_manual_approval" value="1" th:checked="${settings['comment_manual_approval'] == '1'}">
<label class="form-check-label">Comment must be manually approved</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Comment Moderation</label>
<div class="col-sm-6">
<textarea class="form-control" name="moderation_keys" rows="5" th:text="${settings['moderation_keys']}"></textarea>
<small class="form-text text-muted">When a comment contains any of these words in its content, author name, URL, email, IP address, or browser's user agent string, it will be held in the moderation queue. One word or IP address per line.</small>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>General Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">General Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/general/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Site Title</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="site_name" th:value="${settings['site_name']}">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Tagline</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="tagline" th:value="${settings['tagline']}">
<small class="form-text text-muted">In a few words, explain what this site is about.</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Administration Email Address</label>
<div class="col-sm-6">
<input type="email" class="form-control" name="admin_email" th:value="${settings['admin_email']}">
<small class="form-text text-muted">This address is used for admin purposes.</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Timezone</label>
<div class="col-sm-6">
<select class="form-control" name="timezone">
<option value="UTC" th:selected="${settings['timezone'] == 'UTC'}">UTC</option>
<option value="Asia/Ho_Chi_Minh" th:selected="${settings['timezone'] == 'Asia/Ho_Chi_Minh'}">Asia/Ho Chi Minh</option>
<option value="America/New_York" th:selected="${settings['timezone'] == 'America/New_York'}">America/New York</option>
<option value="Europe/London" th:selected="${settings['timezone'] == 'Europe/London'}">Europe/London</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Date Format</label>
<div class="col-sm-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="date_format" value="F j, Y" th:checked="${settings['date_format'] == 'F j, Y'}">
<label class="form-check-label">November 6, 2010 (F j, Y)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="date_format" value="Y-m-d" th:checked="${settings['date_format'] == 'Y-m-d'}">
<label class="form-check-label">2010-11-06 (Y-m-d)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="date_format" value="m/d/Y" th:checked="${settings['date_format'] == 'm/d/Y'}">
<label class="form-check-label">11/06/2010 (m/d/Y)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="date_format" value="d/m/Y" th:checked="${settings['date_format'] == 'd/m/Y'}">
<label class="form-check-label">06/11/2010 (d/m/Y)</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Media Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Media Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/media/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<h5 class="mb-3">Image sizes</h5>
<p class="text-muted small">The sizes listed below determine the maximum dimensions in pixels to use when adding an image to the Media Library.</p>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Thumbnail size</label>
<div class="col-sm-6 form-inline">
<label class="mr-2">Width</label>
<input type="number" class="form-control mr-3" name="thumbnail_size_w" th:value="${settings['thumbnail_size_w'] ?: '150'}" style="width: 80px;">
<label class="mr-2">Height</label>
<input type="number" class="form-control" name="thumbnail_size_h" th:value="${settings['thumbnail_size_h'] ?: '150'}" style="width: 80px;">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Medium size</label>
<div class="col-sm-6 form-inline">
<label class="mr-2">Max Width</label>
<input type="number" class="form-control mr-3" name="medium_size_w" th:value="${settings['medium_size_w'] ?: '300'}" style="width: 80px;">
<label class="mr-2">Max Height</label>
<input type="number" class="form-control" name="medium_size_h" th:value="${settings['medium_size_h'] ?: '300'}" style="width: 80px;">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Large size</label>
<div class="col-sm-6 form-inline">
<label class="mr-2">Max Width</label>
<input type="number" class="form-control mr-3" name="large_size_w" th:value="${settings['large_size_w'] ?: '1024'}" style="width: 80px;">
<label class="mr-2">Max Height</label>
<input type="number" class="form-control" name="large_size_h" th:value="${settings['large_size_h'] ?: '1024'}" style="width: 80px;">
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Permalink Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Permalink Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/permalinks/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<h5 class="mb-3">Common Settings</h5>
<p class="text-muted small">Select the permalink structure for your website. (Note: Since this is a Spring Boot application, changes here define the SEO structure preference but may require developer adjustments in the routing controller to take full effect).</p>
<div class="form-group row">
<div class="col-sm-3 font-weight-bold text-right">Permalink structure</div>
<div class="col-sm-9">
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="permalink_structure" value="plain" th:checked="${settings['permalink_structure'] == 'plain' || settings['permalink_structure'] == null}">
<label class="form-check-label">Plain <code class="ml-2 text-muted">http://localhost:8080/?p=123</code></label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="permalink_structure" value="day_name" th:checked="${settings['permalink_structure'] == 'day_name'}">
<label class="form-check-label">Day and name <code class="ml-2 text-muted">http://localhost:8080/2026/07/02/sample-post/</code></label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="permalink_structure" value="month_name" th:checked="${settings['permalink_structure'] == 'month_name'}">
<label class="form-check-label">Month and name <code class="ml-2 text-muted">http://localhost:8080/2026/07/sample-post/</code></label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="permalink_structure" value="numeric" th:checked="${settings['permalink_structure'] == 'numeric'}">
<label class="form-check-label">Numeric <code class="ml-2 text-muted">http://localhost:8080/archives/123</code></label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="permalink_structure" value="post_name" th:checked="${settings['permalink_structure'] == 'post_name'}">
<label class="form-check-label">Post name <code class="ml-2 text-muted">http://localhost:8080/sample-post/</code></label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Privacy Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Privacy Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/privacy/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<p>As a website owner, you may need to follow national or international privacy laws. For example, you may need to create and display a privacy policy.</p>
<p>Select a Privacy Policy page to be shown on your login and registration pages.</p>
<hr>
<div class="form-group row align-items-center">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Change your Privacy Policy page</label>
<div class="col-sm-4">
<select class="form-control" name="privacy_policy_page">
<option value="">-- Select a Page --</option>
<option value="3" th:selected="${settings['privacy_policy_page'] == '3'}">Privacy Policy</option>
<option value="4" th:selected="${settings['privacy_policy_page'] == '4'}">Terms of Service</option>
</select>
</div>
<div class="col-sm-2">
<button type="submit" class="btn btn-outline-primary">Use This Page</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Reading Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Reading Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/reading/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Your homepage displays</label>
<div class="col-sm-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="show_on_front" value="posts" th:checked="${settings['show_on_front'] == 'posts' || settings['show_on_front'] == null}">
<label class="form-check-label">Your latest posts</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="show_on_front" value="page" th:checked="${settings['show_on_front'] == 'page'}">
<label class="form-check-label">A static page (select below)</label>
</div>
<div class="ml-4 mt-2">
<div class="form-group row mb-2">
<label class="col-sm-4 col-form-label pb-0">Homepage:</label>
<div class="col-sm-8">
<select class="form-control form-control-sm" name="page_on_front">
<option value="">-- Select --</option>
<option value="1" th:selected="${settings['page_on_front'] == '1'}">Sample Page</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label pb-0">Posts page:</label>
<div class="col-sm-8">
<select class="form-control form-control-sm" name="page_for_posts">
<option value="">-- Select --</option>
<option value="2" th:selected="${settings['page_for_posts'] == '2'}">Blog</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Blog pages show at most</label>
<div class="col-sm-2">
<input type="number" class="form-control" name="posts_per_page" th:value="${settings['posts_per_page'] ?: '10'}" min="1">
</div>
<div class="col-sm-4 col-form-label">posts</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Syndication feeds show the most recent</label>
<div class="col-sm-2">
<input type="number" class="form-control" name="posts_per_rss" th:value="${settings['posts_per_rss'] ?: '10'}" min="1">
</div>
<div class="col-sm-4 col-form-label">items</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Search engine visibility</label>
<div class="col-sm-6">
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" name="search_engine_visibility" value="1" th:checked="${settings['search_engine_visibility'] == '1'}">
<label class="form-check-label">Discourage search engines from indexing this site</label>
</div>
<small class="form-text text-muted">It is up to search engines to honor this request.</small>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
<head>
<title>Writing Settings</title>
</head>
<body>
<div layout:fragment="content">
<h1 class="h3 mb-4 text-gray-800">Writing Settings</h1>
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show">
<span th:text="${successMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show">
<span th:text="${errorMessage}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="card shadow mb-4">
<div class="card-body">
<form th:action="@{/manage/settings/writing/save}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Default Post Category</label>
<div class="col-sm-6">
<select class="form-control" name="default_category">
<option value="1" th:selected="${settings['default_category'] == '1'}">Uncategorized</option>
<option value="2" th:selected="${settings['default_category'] == '2'}">News</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Default Post Format</label>
<div class="col-sm-6">
<select class="form-control" name="default_post_format">
<option value="standard" th:selected="${settings['default_post_format'] == 'standard'}">Standard</option>
<option value="aside" th:selected="${settings['default_post_format'] == 'aside'}">Aside</option>
<option value="chat" th:selected="${settings['default_post_format'] == 'chat'}">Chat</option>
<option value="gallery" th:selected="${settings['default_post_format'] == 'gallery'}">Gallery</option>
<option value="link" th:selected="${settings['default_post_format'] == 'link'}">Link</option>
<option value="image" th:selected="${settings['default_post_format'] == 'image'}">Image</option>
<option value="quote" th:selected="${settings['default_post_format'] == 'quote'}">Quote</option>
<option value="status" th:selected="${settings['default_post_format'] == 'status'}">Status</option>
<option value="video" th:selected="${settings['default_post_format'] == 'video'}">Video</option>
<option value="audio" th:selected="${settings['default_post_format'] == 'audio'}">Audio</option>
</select>
</div>
</div>
<hr>
<h5 class="mb-3">Post via email</h5>
<p class="text-muted small">To post to your site by email, you must set up a secret email account with POP3 access. Any mail received at this address will be posted, so it's a good idea to keep this address very secret.</p>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Mail Server</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="mail_server" th:value="${settings['mail_server']}">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Login Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="mail_login" th:value="${settings['mail_login']}">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Password</label>
<div class="col-sm-6">
<input type="password" class="form-control" name="mail_password" th:value="${settings['mail_password']}">
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>