Initial commit: OpenSuite Portal Dashboard
- Express.js + Passport OIDC SSO - Keycloak integration - 5 app tiles: Nextcloud, SOGo, Odoo, Monitoring, Gitea - EJS templating - Session management
This commit is contained in:
commit
3d84692f54
|
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
public/img/logo.eps
|
||||
public/img/logo-hires.png
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const passport = require("passport");
|
||||
const { Strategy } = require("passport-openidconnect");
|
||||
const path = require("path");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: false, httpOnly: true, maxAge: 8 * 60 * 60 * 1000 }
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.use("oidc", new Strategy({
|
||||
issuer: process.env.OIDC_ISSUER,
|
||||
authorizationURL: process.env.OIDC_ISSUER + "/protocol/openid-connect/auth",
|
||||
tokenURL: process.env.OIDC_ISSUER + "/protocol/openid-connect/token",
|
||||
userInfoURL: process.env.OIDC_ISSUER + "/protocol/openid-connect/userinfo",
|
||||
clientID: process.env.OIDC_CLIENT_ID,
|
||||
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
||||
callbackURL: process.env.OIDC_CALLBACK_URL,
|
||||
scope: "openid email profile",
|
||||
passReqToCallback: false,
|
||||
}, (issuer, profile, done) => {
|
||||
return done(null, {
|
||||
username: profile.id,
|
||||
name: profile.displayName,
|
||||
email: profile.emails && profile.emails[0] && profile.emails[0].value,
|
||||
groups: [],
|
||||
});
|
||||
}));
|
||||
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((user, done) => done(null, user));
|
||||
|
||||
app.use("/auth", require("./routes/auth"));
|
||||
app.use("/dashboard", require("./routes/dashboard"));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
if (req.isAuthenticated()) return res.redirect("/dashboard");
|
||||
res.redirect("/auth/login");
|
||||
});
|
||||
|
||||
app.get("/health", (req, res) => res.json({ status: "ok" }));
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log("Portal running on port " + process.env.PORT);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "opensuite-portal",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.15.2",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"ejs": "^5.0.2",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-openidconnect": "^0.1.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xml:space="preserve"
|
||||
width="534.66669"
|
||||
height="214.66667"
|
||||
viewBox="0 0 534.66669 214.66667"
|
||||
sodipodi:docname="logo.eps"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs6" /><sodipodi:namedview
|
||||
id="namedview4"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0" /><g
|
||||
id="g8"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="ink_ext_XXXXXX"
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,0,214.66667)"><g
|
||||
id="g10"
|
||||
transform="scale(0.1)"><path
|
||||
d="M 4010,0 H 6.51563 V 1610 H 4010 V 0"
|
||||
style="fill:#555658;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path12" /><path
|
||||
d="m 1711.5,762.699 25.76,-23.094 c -31.47,-28.691 -64.76,-43.039 -99.86,-43.039 -37.56,0 -69.57,13.239 -96.05,39.719 -26.47,26.469 -39.72,58.485 -39.72,96.039 0,37.442 13.25,69.453 39.72,96.039 26.48,26.59 58.49,39.879 96.05,39.879 35.1,0 68.39,-14.344 99.86,-43.035 l -25.76,-23.43 c -23.71,21.047 -48.41,31.575 -74.1,31.575 -27.82,0 -51.61,-9.864 -71.37,-29.579 -19.78,-19.718 -29.67,-43.535 -29.67,-71.449 0,-28.027 9.89,-51.898 29.67,-71.613 19.76,-19.719 43.55,-29.578 71.37,-29.578 25.69,0 50.39,10.523 74.1,31.566"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path14" /><path
|
||||
d="M 1787.67,965.422 V 699.395 h -34.57 v 266.027 h 34.57"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path16" /><path
|
||||
d="m 1854.61,831.496 c -11.57,-11.633 -17.36,-25.594 -17.36,-41.875 0,-16.398 5.79,-30.351 17.36,-41.871 11.58,-11.523 25.56,-17.281 41.97,-17.281 16.38,0 30.37,5.758 41.95,17.281 11.56,11.52 17.36,25.473 17.36,41.871 0,16.281 -5.8,30.242 -17.36,41.875 -11.58,11.629 -25.57,17.445 -41.95,17.445 -16.41,0 -30.39,-5.816 -41.97,-17.445 z m 41.97,52.836 c 26.13,0 48.42,-9.246 66.87,-27.75 18.44,-18.5 27.66,-40.82 27.66,-66.961 0,-26.144 -9.22,-48.465 -27.66,-66.965 -18.45,-18.5 -40.74,-27.75 -66.87,-27.75 -26.04,0 -48.3,9.25 -66.8,27.75 -18.51,18.5 -27.75,40.821 -27.75,66.965 0,26.141 9.24,48.461 27.75,66.961 18.5,18.504 40.76,27.75 66.8,27.75"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path18" /><path
|
||||
d="m 2077.91,697.07 c -21.59,0 -38.99,6.586 -52.17,19.774 -13.19,13.179 -19.94,29.683 -20.28,49.515 v 104.02 h 34.57 V 767.688 c 0,-9.86 3.6,-18.336 10.8,-25.422 7.2,-7.09 16.18,-10.633 26.92,-10.633 9.86,0 18.42,3.515 25.67,10.551 7.26,7.031 10.89,15.531 10.89,25.504 v 102.691 h 34.39 v -104.02 c -0.33,-19.832 -7.04,-36.336 -20.11,-49.515 -13.19,-13.188 -30.07,-19.774 -50.68,-19.774"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path20" /><path
|
||||
d="m 2321.41,776.828 v 25.418 c -1.99,10.746 -7.37,20.492 -16.12,29.25 -11.53,11.629 -25.47,17.445 -41.87,17.445 -16.39,0 -30.38,-5.816 -41.96,-17.445 -11.57,-11.633 -17.36,-25.594 -17.36,-41.875 0,-16.398 5.79,-30.351 17.36,-41.871 11.58,-11.523 25.57,-17.281 41.96,-17.281 16.4,0 30.34,5.758 41.87,17.281 8.75,8.75 14.13,18.441 16.12,29.078 z m 0,81.746 v 106.68 h 34.56 V 699.395 h -34.56 v 21.269 c -16.61,-17.172 -35.95,-25.758 -57.99,-25.758 -26.14,0 -48.44,9.25 -66.88,27.75 -18.44,18.5 -27.66,40.821 -27.66,66.965 0,26.141 9.22,48.461 27.66,66.961 18.44,18.504 40.74,27.75 66.88,27.75 22.04,0 41.38,-8.586 57.99,-25.758"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path22" /><path
|
||||
d="m 2414.69,809.395 v -110 h -34.4 v 265.859 h 34.4 V 843.789 h 82.08 v 121.465 h 34.4 V 699.395 h -34.4 v 110 h -82.08"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path24" /><path
|
||||
d="m 2598.12,831.496 c -11.58,-11.633 -17.36,-25.594 -17.36,-41.875 0,-16.398 5.78,-30.351 17.36,-41.871 11.56,-11.523 25.56,-17.281 41.95,-17.281 16.39,0 30.37,5.758 41.96,17.281 11.57,11.52 17.36,25.473 17.36,41.871 0,16.281 -5.79,30.242 -17.36,41.875 -11.59,11.629 -25.57,17.445 -41.96,17.445 -16.39,0 -30.39,-5.816 -41.95,-17.445 z m 41.95,52.836 c 26.13,0 48.44,-9.246 66.88,-27.75 18.44,-18.5 27.66,-40.82 27.66,-66.961 0,-26.144 -9.22,-48.465 -27.66,-66.965 -18.44,-18.5 -40.75,-27.75 -66.88,-27.75 -26.03,0 -48.3,9.25 -66.79,27.75 -18.5,18.5 -27.76,40.821 -27.76,66.965 0,26.141 9.26,48.461 27.76,66.961 18.49,18.504 40.76,27.75 66.79,27.75"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path26" /><path
|
||||
d="m 2761.48,737.848 c 9.53,-5.785 27.56,-11.91 42.54,-11.91 18.37,0 26.54,7.488 26.54,18.375 0,11.226 -6.82,17.011 -27.23,24.16 -32.32,11.226 -45.93,28.922 -45.59,48.316 0,29.262 24.16,52.059 62.61,52.059 18.37,0 34.37,-4.758 43.89,-9.868 l -8.17,-29.597 c -7.14,4.082 -20.41,9.523 -35.04,9.523 -14.98,0 -23.15,-7.148 -23.15,-17.351 0,-10.551 7.84,-15.657 28.93,-23.141 29.94,-10.891 43.9,-26.199 44.24,-50.699 0,-29.945 -23.48,-51.723 -67.37,-51.723 -20.08,0 -38.12,4.766 -50.37,11.571 l 8.17,30.285"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path28" /><path
|
||||
d="m 2944.9,912.402 v -47.293 h 39.8 v -31.304 h -39.8 v -73.16 c 0,-20.079 5.44,-30.625 21.43,-30.625 7.14,0 12.59,1.023 16.34,2.043 l 0.67,-31.989 c -6.12,-2.379 -17.01,-4.082 -30.28,-4.082 -15.31,0 -28.24,5.102 -36.07,13.613 -8.85,9.528 -12.93,24.497 -12.93,46.274 v 77.926 h -23.81 v 31.304 h 23.81 v 37.426 l 40.84,9.867"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path30" /><path
|
||||
d="m 1195.93,653.195 c -49.76,0 -99.57,-1.422 -149.26,0.36 -62.572,2.242 -113.143,46.812 -122.533,104.797 -11.18,69.019 17.406,127.054 79.033,150.535 25.27,9.621 43.16,23.492 60.66,43.535 78.69,90.158 221.21,71.958 276.42,-35.492 13.13,-25.563 27.33,-42.922 55.06,-54.735 50.17,-21.363 73.92,-73.929 62.23,-122.515 -12.48,-51.879 -51.92,-84.867 -107.19,-86.41 -51.44,-1.438 -102.94,-0.305 -154.42,-0.305 0,0.074 0,0.152 0,0.23"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path32" /><path
|
||||
d="M 1127.45,891.926 V 724.574 h -21.66 v 167.352 h 21.66"
|
||||
style="fill:#5988ce;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path34" /><path
|
||||
d="m 1249.07,773.316 v 16 c -1.26,6.762 -4.64,12.903 -10.15,18.411 -7.25,7.324 -16.04,10.98 -26.35,10.98 -10.33,0 -19.13,-3.656 -26.41,-10.98 -7.29,-7.321 -10.93,-16.11 -10.93,-26.36 0,-10.316 3.64,-19.105 10.93,-26.355 7.28,-7.25 16.08,-10.875 26.41,-10.875 10.31,0 19.1,3.625 26.35,10.875 5.51,5.508 8.89,11.609 10.15,18.304 z m 0,51.461 v 67.149 h 21.75 V 724.574 h -21.75 v 13.391 c -10.46,-10.813 -22.63,-16.211 -36.5,-16.211 -16.46,0 -30.49,5.82 -42.11,17.465 -11.6,11.644 -17.4,25.695 -17.4,42.148 0,16.457 5.8,30.508 17.4,42.153 11.62,11.64 25.65,17.468 42.11,17.468 13.87,0 26.04,-5.406 36.5,-16.211"
|
||||
style="fill:#5988ce;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path36" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
|
|
@ -0,0 +1,20 @@
|
|||
const express = require("express");
|
||||
const passport = require("passport");
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/login", passport.authenticate("oidc"));
|
||||
|
||||
router.get("/callback",
|
||||
passport.authenticate("oidc", { failureRedirect: "/auth/login" }),
|
||||
(req, res) => res.redirect("/dashboard")
|
||||
);
|
||||
|
||||
router.get("/logout", (req, res) => {
|
||||
req.logout(() => {
|
||||
req.session.destroy(() => {
|
||||
res.redirect("https://auth.lookatme.my.id/realms/lookatme/protocol/openid-connect/logout?post_logout_redirect_uri=https://portal.lookatme.my.id&client_id=portal");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.isAuthenticated()) return res.redirect("/auth/login");
|
||||
next();
|
||||
};
|
||||
|
||||
router.get("/", requireAuth, (req, res) => {
|
||||
const user = req.user;
|
||||
const apps = [
|
||||
{ id:"nextcloud", name:"Nextcloud Drive", desc:"File & dokumen bersama", url:process.env.URL_NEXTCLOUD, icon:"☁️", color:"#E6F1FB" },
|
||||
{ id:"sogo", name:"SOGo Mail", desc:"Email & kalender tim", url:process.env.URL_SOGO+"/SOGo", icon:"✉️", color:"#E1F5EE" },
|
||||
{ id:"odoo", name:"Odoo ERP", desc:"HR, CRM, Project", url:process.env.URL_ODOO, icon:"📊", color:"#EEEDFE" },
|
||||
{ id:"monitoring", name:"Monitoring", desc:"Grafana dashboard", url:process.env.URL_MONITORING, icon:"📈", color:"#FEF3E2" },
|
||||
{ id:"gitea", name:"Gitea Dev", desc:"Git repository", url:process.env.URL_GITEA, icon:"🐙", color:"#F0F0F0" },
|
||||
];
|
||||
res.render("dashboard", {
|
||||
user, apps,
|
||||
brandName: process.env.BRAND_NAME || "OpenSuite",
|
||||
brandColor: process.env.BRAND_COLOR || "#1A56DB",
|
||||
title: "Dashboard — " + (process.env.BRAND_NAME || "OpenSuite"),
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title><%= title %></title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f7fa; }
|
||||
.topbar { background: <%= brandColor %>; color: white; padding: 18px 24px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
|
||||
.topbar-brand { font-size: 20px; font-weight: 600; letter-spacing: 0.5px; }
|
||||
.topbar-user { display: flex; align-items: center; gap: 12px; font-size: 14px; }
|
||||
.avatar { width: 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; }
|
||||
.logout-btn { background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.4); color: white; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; text-decoration: none; }
|
||||
.logout-btn:hover { background: rgba(255,255,255,0.3); }
|
||||
.main { max-width: 1100px; margin: 0 auto; padding: 32px 24px; }
|
||||
.welcome { margin-bottom: 28px; }
|
||||
.welcome h1 { font-size: 22px; color: #1a1a2e; font-weight: 600; }
|
||||
.welcome p { font-size: 14px; color: #666; margin-top: 4px; }
|
||||
.section-title { font-size: 12px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 14px; }
|
||||
.app-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
||||
.app-card { background: white; border-radius: 12px; padding: 20px; cursor: pointer; transition: all 0.2s; border: 1px solid #e8ecf0; text-decoration: none; display: block; }
|
||||
.app-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.10); border-color: <%= brandColor %>; }
|
||||
.app-icon { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 12px; }
|
||||
.app-name { font-size: 14px; font-weight: 600; color: #1a1a2e; margin-bottom: 4px; }
|
||||
.app-desc { font-size: 12px; color: #888; }
|
||||
.status-bar { background: white; border-radius: 12px; padding: 16px 20px; border: 1px solid #e8ecf0; display: flex; align-items: center; gap: 8px; font-size: 13px; color: #444; }
|
||||
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }
|
||||
.sso-badge { background: #e1f5ee; color: #085041; font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 99px; margin-left: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<div class="topbar-brand">🚀 OpenSuite</div>
|
||||
<div class="topbar-user">
|
||||
<div class="avatar"><%= user.name.slice(0,2).toUpperCase() %></div>
|
||||
<span><%= user.name %></span>
|
||||
<a href="/auth/logout" class="logout-btn">Keluar</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="welcome">
|
||||
<h1>Selamat datang, <%= user.name.split(' ')[0] %> 👋</h1>
|
||||
<p>Akses semua aplikasi OpenSuite dari satu tempat</p>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Aplikasi</div>
|
||||
<div class="app-grid">
|
||||
<% apps.forEach(app => { %>
|
||||
<a href="<%= app.url %>" class="app-card" target="_blank">
|
||||
<div class="app-icon" style="background:<%= app.color %>">
|
||||
<%= app.icon %>
|
||||
</div>
|
||||
<div class="app-name"><%= app.name %></div>
|
||||
<div class="app-desc"><%= app.desc %></div>
|
||||
</a>
|
||||
<% }) %>
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
<div class="status-dot"></div>
|
||||
<span>Semua sistem berjalan normal</span>
|
||||
<span class="sso-badge">SSO Aktif · <%= user.email %></span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue