mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Add staging banner
This commit is contained in:
@@ -28,6 +28,7 @@ Optional:
|
|||||||
| Variable | Description | Example Value |
|
| Variable | Description | Example Value |
|
||||||
|----------|-------------|---------------|
|
|----------|-------------|---------------|
|
||||||
| `IMGFLOAT_COMMIT_URL_PREFIX` | Git commit URL prefix used for the build link badge (unset to hide the badge) | https://github.com/imgfloat/server/commit/ |
|
| `IMGFLOAT_COMMIT_URL_PREFIX` | Git commit URL prefix used for the build link badge (unset to hide the badge) | https://github.com/imgfloat/server/commit/ |
|
||||||
|
| `IMGFLOAT_IS_STAGING` | Show a staging warning banner on non-broadcast pages when set to `1` | 1 |
|
||||||
| `IMGFLOAT_MARKETPLACE_SCRIPTS_PATH` | Filesystem path to marketplace script seed directories (each containing `metadata.json`, optional `source.js`, optional `logo.png`, and optional `attachments/`) | /var/imgfloat/marketplace-scripts |
|
| `IMGFLOAT_MARKETPLACE_SCRIPTS_PATH` | Filesystem path to marketplace script seed directories (each containing `metadata.json`, optional `source.js`, optional `logo.png`, and optional `attachments/`) | /var/imgfloat/marketplace-scripts |
|
||||||
| `IMGFLOAT_SYSADMIN_CHANNEL_ACCESS_ENABLED` | Allow sysadmins to manage any channel without being listed as a channel admin | true |
|
| `IMGFLOAT_SYSADMIN_CHANNEL_ACCESS_ENABLED` | Allow sysadmins to manage any channel without being listed as a channel admin | true |
|
||||||
| `TWITCH_REDIRECT_URI` | Override default redirect URI | http://localhost:8080/login/oauth2/code/twitch |
|
| `TWITCH_REDIRECT_URI` | Override default redirect URI | http://localhost:8080/login/oauth2/code/twitch |
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import dev.kruhlmann.imgfloat.service.VersionService;
|
|||||||
import dev.kruhlmann.imgfloat.util.LogSanitizer;
|
import dev.kruhlmann.imgfloat.util.LogSanitizer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -34,6 +35,7 @@ public class ViewController {
|
|||||||
private final GithubReleaseService githubReleaseService;
|
private final GithubReleaseService githubReleaseService;
|
||||||
private final SystemAdministratorService systemAdministratorService;
|
private final SystemAdministratorService systemAdministratorService;
|
||||||
private final long uploadLimitBytes;
|
private final long uploadLimitBytes;
|
||||||
|
private final boolean isStaging;
|
||||||
|
|
||||||
public ViewController(
|
public ViewController(
|
||||||
ChannelDirectoryService channelDirectoryService,
|
ChannelDirectoryService channelDirectoryService,
|
||||||
@@ -44,7 +46,8 @@ public class ViewController {
|
|||||||
AuthorizationService authorizationService,
|
AuthorizationService authorizationService,
|
||||||
GithubReleaseService githubReleaseService,
|
GithubReleaseService githubReleaseService,
|
||||||
SystemAdministratorService systemAdministratorService,
|
SystemAdministratorService systemAdministratorService,
|
||||||
long uploadLimitBytes
|
long uploadLimitBytes,
|
||||||
|
@Value("${IMGFLOAT_IS_STAGING:0}") String isStagingFlag
|
||||||
) {
|
) {
|
||||||
this.channelDirectoryService = channelDirectoryService;
|
this.channelDirectoryService = channelDirectoryService;
|
||||||
this.versionService = versionService;
|
this.versionService = versionService;
|
||||||
@@ -55,6 +58,7 @@ public class ViewController {
|
|||||||
this.githubReleaseService = githubReleaseService;
|
this.githubReleaseService = githubReleaseService;
|
||||||
this.systemAdministratorService = systemAdministratorService;
|
this.systemAdministratorService = systemAdministratorService;
|
||||||
this.uploadLimitBytes = uploadLimitBytes;
|
this.uploadLimitBytes = uploadLimitBytes;
|
||||||
|
this.isStaging = "1".equals(isStagingFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@org.springframework.web.bind.annotation.GetMapping("/")
|
@org.springframework.web.bind.annotation.GetMapping("/")
|
||||||
@@ -67,9 +71,11 @@ public class ViewController {
|
|||||||
model.addAttribute("channel", sessionUsername);
|
model.addAttribute("channel", sessionUsername);
|
||||||
model.addAttribute("adminChannels", channelDirectoryService.adminChannelsFor(sessionUsername));
|
model.addAttribute("adminChannels", channelDirectoryService.adminChannelsFor(sessionUsername));
|
||||||
model.addAttribute("isSystemAdmin", authorizationService.userIsSystemAdministrator(sessionUsername));
|
model.addAttribute("isSystemAdmin", authorizationService.userIsSystemAdministrator(sessionUsername));
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "dashboard";
|
return "dashboard";
|
||||||
}
|
}
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
@@ -77,6 +83,7 @@ public class ViewController {
|
|||||||
@org.springframework.web.bind.annotation.GetMapping("/channels")
|
@org.springframework.web.bind.annotation.GetMapping("/channels")
|
||||||
public String channelDirectory(Model model) {
|
public String channelDirectory(Model model) {
|
||||||
LOG.info("Rendering channel directory");
|
LOG.info("Rendering channel directory");
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "channels";
|
return "channels";
|
||||||
}
|
}
|
||||||
@@ -84,6 +91,7 @@ public class ViewController {
|
|||||||
@org.springframework.web.bind.annotation.GetMapping("/terms")
|
@org.springframework.web.bind.annotation.GetMapping("/terms")
|
||||||
public String termsOfUse(Model model) {
|
public String termsOfUse(Model model) {
|
||||||
LOG.info("Rendering terms of use");
|
LOG.info("Rendering terms of use");
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "terms";
|
return "terms";
|
||||||
}
|
}
|
||||||
@@ -91,6 +99,7 @@ public class ViewController {
|
|||||||
@org.springframework.web.bind.annotation.GetMapping("/privacy")
|
@org.springframework.web.bind.annotation.GetMapping("/privacy")
|
||||||
public String privacyPolicy(Model model) {
|
public String privacyPolicy(Model model) {
|
||||||
LOG.info("Rendering privacy policy");
|
LOG.info("Rendering privacy policy");
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "privacy";
|
return "privacy";
|
||||||
}
|
}
|
||||||
@@ -98,6 +107,7 @@ public class ViewController {
|
|||||||
@org.springframework.web.bind.annotation.GetMapping("/cookies")
|
@org.springframework.web.bind.annotation.GetMapping("/cookies")
|
||||||
public String cookiePolicy(Model model) {
|
public String cookiePolicy(Model model) {
|
||||||
LOG.info("Rendering cookie policy");
|
LOG.info("Rendering cookie policy");
|
||||||
|
addStagingAttribute(model);
|
||||||
addVersionAttributes(model);
|
addVersionAttributes(model);
|
||||||
return "cookies";
|
return "cookies";
|
||||||
}
|
}
|
||||||
@@ -116,6 +126,7 @@ public class ViewController {
|
|||||||
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings");
|
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings");
|
||||||
}
|
}
|
||||||
model.addAttribute("initialSysadmin", systemAdministratorService.getInitialSysadmin());
|
model.addAttribute("initialSysadmin", systemAdministratorService.getInitialSysadmin());
|
||||||
|
addStagingAttribute(model);
|
||||||
return "settings";
|
return "settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +155,7 @@ public class ViewController {
|
|||||||
LOG.error("Failed to serialize settings for admin view", e);
|
LOG.error("Failed to serialize settings for admin view", e);
|
||||||
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings");
|
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings");
|
||||||
}
|
}
|
||||||
|
addStagingAttribute(model);
|
||||||
|
|
||||||
return "admin";
|
return "admin";
|
||||||
}
|
}
|
||||||
@@ -167,4 +179,8 @@ public class ViewController {
|
|||||||
model.addAttribute("buildCommitShort", gitInfoService.getShortCommitSha());
|
model.addAttribute("buildCommitShort", gitInfoService.getShortCommitSha());
|
||||||
model.addAttribute("buildCommitUrl", gitInfoService.getCommitUrl());
|
model.addAttribute("buildCommitUrl", gitInfoService.getCommitUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addStagingAttribute(Model model) {
|
||||||
|
model.addAttribute("isStaging", isStaging);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,34 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.has-staging-banner {
|
||||||
|
padding-top: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staging-banner {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
background: repeating-linear-gradient(135deg, #111827 0 18px, #facc15 18px 36px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.staging-banner span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #111827;
|
||||||
|
color: #facc15;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.landing-body {
|
.landing-body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background:
|
background:
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
<script src="/js/vendor/OBJLoader.js"></script>
|
<script src="/js/vendor/OBJLoader.js"></script>
|
||||||
<script src="/js/csrf.js"></script>
|
<script src="/js/csrf.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="admin-body">
|
<body class="admin-body" th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<div class="admin-frame">
|
<div class="admin-frame">
|
||||||
<header class="admin-topbar">
|
<header class="admin-topbar">
|
||||||
<div class="topbar-left">
|
<div class="topbar-left">
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<main class="landing">
|
<main class="landing">
|
||||||
<header class="landing-header">
|
<header class="landing-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="dashboard-body">
|
<body class="dashboard-body" th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<div class="dashboard-shell">
|
<div class="dashboard-shell">
|
||||||
<header class="dashboard-topbar">
|
<header class="dashboard-topbar">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="error-body">
|
<body class="error-body" th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<div class="error-shell">
|
<div class="error-shell">
|
||||||
<header class="error-header">
|
<header class="error-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
8
src/main/resources/templates/fragments/staging.html
Normal file
8
src/main/resources/templates/fragments/staging.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<body>
|
||||||
|
<div th:fragment="banner" th:if="${isStaging}" class="staging-banner" role="status">
|
||||||
|
<span>Staging environment</span>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="landing-body">
|
<body class="landing-body" th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<div class="landing">
|
<div class="landing">
|
||||||
<header class="landing-header">
|
<header class="landing-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<main class="landing">
|
<main class="landing">
|
||||||
<header class="landing-header">
|
<header class="landing-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
|
||||||
<script src="/js/csrf.js"></script>
|
<script src="/js/csrf.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="settings-body">
|
<body class="settings-body" th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<div class="settings-shell">
|
<div class="settings-shell">
|
||||||
<header class="settings-header">
|
<header class="settings-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/styles.css" />
|
<link rel="stylesheet" href="/css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body th:classappend="${isStaging} ? ' has-staging-banner' : ''">
|
||||||
|
<div th:insert="~{fragments/staging :: banner}"></div>
|
||||||
<main class="landing">
|
<main class="landing">
|
||||||
<header class="landing-header">
|
<header class="landing-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
|
|||||||
Reference in New Issue
Block a user