commit
fb97dc15f8
23 changed files with 16449 additions and 0 deletions
@ -0,0 +1,23 @@ |
|||||||
|
# syntax=docker/dockerfile:1 |
||||||
|
|
||||||
|
FROM node:18-alpine AS builder |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
COPY . . |
||||||
|
RUN npm install |
||||||
|
|
||||||
|
ENV NODE_ENV=production |
||||||
|
|
||||||
|
RUN npm run build |
||||||
|
|
||||||
|
FROM node:18-alpine |
||||||
|
|
||||||
|
ENV NODE_ENV=production |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
COPY package.json . |
||||||
|
COPY package-lock.json . |
||||||
|
RUN npm install |
||||||
|
COPY --from=builder /app/dist . |
||||||
|
|
||||||
|
CMD ["node", "./server/server.js"] |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
{ |
||||||
|
"name": "vpn-portal", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "Portal for openvpn auth and profiles", |
||||||
|
"scripts": { |
||||||
|
"build": "run-s build:server build:web", |
||||||
|
"build:server": "webpack --config webpack.server.production.js", |
||||||
|
"build:web": "webpack --config webpack.web.production.js", |
||||||
|
"build:server:once": "webpack --config webpack.server.development.js", |
||||||
|
"dev:server": "npm run build:server:once && run-p nodemon:prod watch:server", |
||||||
|
"watch:server": "webpack --config webpack.server.development.js --watch", |
||||||
|
"nodemon:prod": "nodemon dist/server/server.js" |
||||||
|
}, |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"dependencies": { |
||||||
|
"@webcomponents/webcomponentsjs": "^2.7.0", |
||||||
|
"express": "^4.18.2", |
||||||
|
"haunted": "^5.0.0", |
||||||
|
"lit-html": "^2.6.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/express": "^4.17.17", |
||||||
|
"@types/webpack-dev-middleware": "^5.3.0", |
||||||
|
"@types/webpack-hot-middleware": "^2.25.6", |
||||||
|
"@typescript-eslint/eslint-plugin": "^5.51.0", |
||||||
|
"@typescript-eslint/parser": "^5.51.0", |
||||||
|
"clean-webpack-plugin": "^4.0.0", |
||||||
|
"copy-webpack-plugin": "^11.0.0", |
||||||
|
"css-loader": "^6.7.3", |
||||||
|
"dotenv-webpack": "^8.0.1", |
||||||
|
"eslint": "^8.33.0", |
||||||
|
"extract-loader": "^5.1.0", |
||||||
|
"html-webpack-plugin": "^5.5.0", |
||||||
|
"node-sass": "^8.0.0", |
||||||
|
"nodemon": "^2.0.20", |
||||||
|
"npm-run-all": "^4.1.5", |
||||||
|
"sass-loader": "^13.2.0", |
||||||
|
"ts-loader": "^9.4.2", |
||||||
|
"typescript": "^4.9.5", |
||||||
|
"webpack": "^5.75.0", |
||||||
|
"webpack-cli": "^5.0.1", |
||||||
|
"webpack-dev-middleware": "^6.0.1", |
||||||
|
"webpack-hot-middleware": "^2.25.3", |
||||||
|
"webpack-merge": "^5.8.0", |
||||||
|
"webpack-node-externals": "^3.0.0" |
||||||
|
}, |
||||||
|
"nodemonConfig": { |
||||||
|
"exec": "node -r dotenv/config", |
||||||
|
"watch": [ |
||||||
|
"dist/server" |
||||||
|
] |
||||||
|
}, |
||||||
|
"eslintConfig": { |
||||||
|
"parser": "@typescript-eslint/parser", |
||||||
|
"extends": [ |
||||||
|
"eslint:recommended", |
||||||
|
"plugin:@typescript-eslint/eslint-recommended", |
||||||
|
"plugin:@typescript-eslint/recommended" |
||||||
|
], |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 2018, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"@typescript-eslint/no-explicit-any": 0, |
||||||
|
"@typescript-eslint/explicit-function-return-type": 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title> |
||||||
|
<script src="webcomponents-loader.js"></script> |
||||||
|
</head> |
||||||
|
<body></body> |
||||||
|
</html> |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
import express from "express"; |
||||||
|
|
||||||
|
const router = express.Router(); |
||||||
|
|
||||||
|
export default router; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
import express from "express"; |
||||||
|
import router from "./router"; |
||||||
|
import webpack from "webpack"; |
||||||
|
import webpackDevMiddleware from "webpack-dev-middleware"; |
||||||
|
import webpackHotMiddleware from "webpack-hot-middleware"; |
||||||
|
import webpackConfig from "../../webpack.web.development.js"; |
||||||
|
|
||||||
|
const app = express(); |
||||||
|
|
||||||
|
const compiler = webpack(webpackConfig); |
||||||
|
|
||||||
|
app.use(webpackDevMiddleware(compiler)); |
||||||
|
app.use(webpackHotMiddleware(compiler)); |
||||||
|
app.use(router); |
||||||
|
|
||||||
|
app.listen(8080, () => { |
||||||
|
console.log("Server started"); |
||||||
|
}); |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
import express from "express"; |
||||||
|
import router from "./router"; |
||||||
|
import path from "path"; |
||||||
|
|
||||||
|
const app = express(); |
||||||
|
|
||||||
|
app.use(express.static(path.join(__dirname, "..", "web"))); |
||||||
|
app.use(router); |
||||||
|
|
||||||
|
const server = app.listen(8080, () => { |
||||||
|
console.log("Server started"); |
||||||
|
}); |
||||||
|
|
||||||
|
process.on("SIGTERM", () => { |
||||||
|
server.close(); |
||||||
|
}); |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
import { unsafeCSS, supportsAdoptingStyleSheets, CSSResult } from "lit-element"; |
||||||
|
import { useState, useEffect, useLayoutEffect, useMemo, useRef } from "haunted"; |
||||||
|
|
||||||
|
export function useStylesheet(initialStyles: CSSResult[] | CSSResult) { |
||||||
|
const [needsShim, setNeedsShim] = useState(false); |
||||||
|
const userStyles = useRef(initialStyles); |
||||||
|
|
||||||
|
const styles = useMemo(() => { |
||||||
|
let _styles: CSSResult[]; |
||||||
|
|
||||||
|
if (Array.isArray(userStyles.current)) { |
||||||
|
// De-duplicate styles preserving the _last_ instance in the set.
|
||||||
|
// This is a performance optimization to avoid duplicated styles that can
|
||||||
|
// occur especially when composing via subclassing.
|
||||||
|
// The last item is kept to try to preserve the cascade order with the
|
||||||
|
// assumption that it's most important that last added styles override
|
||||||
|
// previous styles.
|
||||||
|
const addStyles = (styles: CSSResult[], set: Set<CSSResult>) => |
||||||
|
styles.reduceRight( |
||||||
|
(set, s) => |
||||||
|
// Note: On IE set.add() does not return the set
|
||||||
|
Array.isArray(s) ? addStyles(s, set) : (set.add(s), set), |
||||||
|
set |
||||||
|
); |
||||||
|
// Array.from does not work on Set in IE, otherwise return
|
||||||
|
// Array.from(addStyles(userStyles, new Set<CSSResult>())).reverse()
|
||||||
|
const set = addStyles(userStyles.current, new Set<CSSResult>()); |
||||||
|
const styles: CSSResult[] = []; |
||||||
|
set.forEach((v) => styles.unshift(v)); |
||||||
|
_styles = styles; |
||||||
|
} else { |
||||||
|
_styles = userStyles.current === undefined ? [] : [userStyles.current]; |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure that there are no invalid CSSStyleSheet instances here. They are
|
||||||
|
// invalid in two conditions.
|
||||||
|
// (1) the sheet is non-constructible (`sheet` of a HTMLStyleElement), but
|
||||||
|
// this is impossible to check except via .replaceSync or use
|
||||||
|
// (2) the ShadyCSS polyfill is enabled (:. supportsAdoptingStyleSheets is
|
||||||
|
// false)
|
||||||
|
return _styles.map((s) => { |
||||||
|
if (s instanceof CSSStyleSheet && !supportsAdoptingStyleSheets) { |
||||||
|
// Flatten the cssText from the passed constructible stylesheet (or
|
||||||
|
// undetectable non-constructible stylesheet). The user might have
|
||||||
|
// expected to update their stylesheets over time, but the alternative
|
||||||
|
// is a crash.
|
||||||
|
const cssText: string = Array.prototype.slice |
||||||
|
.call(s.cssRules) |
||||||
|
.reduce((css: string, rule: CSSRule) => css + rule.cssText, ""); |
||||||
|
return unsafeCSS(cssText); |
||||||
|
} |
||||||
|
return s; |
||||||
|
}); |
||||||
|
}, [userStyles.current]); |
||||||
|
|
||||||
|
useLayoutEffect( |
||||||
|
function () { |
||||||
|
const elm = this.host as Element; |
||||||
|
// There are three separate cases here based on Shadow DOM support.
|
||||||
|
// (1) shadowRoot polyfilled: use ShadyCSS
|
||||||
|
// (2) shadowRoot.adoptedStyleSheets available: use it
|
||||||
|
// (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after
|
||||||
|
// rendering
|
||||||
|
if ( |
||||||
|
window["ShadyCSS"] !== undefined && |
||||||
|
!window["ShadyCSS"].nativeShadow |
||||||
|
) { |
||||||
|
window["ShadyCSS"].ScopingShim.prepareAdoptedCssText( |
||||||
|
styles.map((s) => (s instanceof CSSResult ? s.cssText : "")), |
||||||
|
elm.localName |
||||||
|
); |
||||||
|
} else if (supportsAdoptingStyleSheets) { |
||||||
|
elm.shadowRoot!.adoptedStyleSheets = styles.map((s) => |
||||||
|
s instanceof CSSStyleSheet ? s : s.styleSheet! |
||||||
|
); |
||||||
|
} else { |
||||||
|
setNeedsShim(true); |
||||||
|
} |
||||||
|
}, |
||||||
|
[styles] |
||||||
|
); |
||||||
|
|
||||||
|
useEffect( |
||||||
|
function () { |
||||||
|
const elm = this.host as Element; |
||||||
|
if (needsShim) { |
||||||
|
styles.forEach((s) => { |
||||||
|
const style = document.createElement("style"); |
||||||
|
style.textContent = s.cssText; |
||||||
|
elm.shadowRoot!.appendChild(style); |
||||||
|
}); |
||||||
|
setNeedsShim(false); |
||||||
|
} |
||||||
|
}, |
||||||
|
[needsShim, styles] |
||||||
|
); |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
:host { |
||||||
|
font-family: Ubuntu, sans-serif; |
||||||
|
display: block; |
||||||
|
border: solid 1px black; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
color: #77216f; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
border-radius: 4px; |
||||||
|
padding: 8px; |
||||||
|
font-size: 16px; |
||||||
|
font-weight: bold; |
||||||
|
border: 0; |
||||||
|
background-color: #aea79f; |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
import { component, useState } from "haunted"; |
||||||
|
import { html, render } from "lit-html"; |
||||||
|
import { css, unsafeCSS } from "lit-element"; |
||||||
|
import { useStylesheet } from "./hooks/css"; |
||||||
|
import style from "./main.scss"; |
||||||
|
|
||||||
|
const CSS = css` |
||||||
|
${unsafeCSS(style)} |
||||||
|
`;
|
||||||
|
|
||||||
|
if (module["hot"]) { |
||||||
|
module["hot"].accept(() => { |
||||||
|
window.location.reload(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
const AppComp = () => { |
||||||
|
useStylesheet(CSS); |
||||||
|
const [counter, setCounter] = useState(0); |
||||||
|
|
||||||
|
return html` |
||||||
|
<h1>My App</h1> |
||||||
|
<p>Value = ${counter}</p> |
||||||
|
<button |
||||||
|
@click=${() => { |
||||||
|
setCounter(counter + 1); |
||||||
|
}} |
||||||
|
> |
||||||
|
increment |
||||||
|
</button> |
||||||
|
`;
|
||||||
|
}; |
||||||
|
|
||||||
|
customElements.define("my-app", component(AppComp)); |
||||||
|
|
||||||
|
render(html`<my-app></my-app>`, document.body); |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
// typings.d.ts
|
||||||
|
declare module '*.scss' { |
||||||
|
const content: string; |
||||||
|
export default content; |
||||||
|
} |
||||||
|
|
||||||
|
declare module '*.png' { |
||||||
|
const content: string; |
||||||
|
export default content; |
||||||
|
} |
||||||
|
|
||||||
|
declare module '*.jpg' { |
||||||
|
const content: string; |
||||||
|
export default content; |
||||||
|
} |
||||||
|
|
||||||
|
declare module '*.gif' { |
||||||
|
const content: string; |
||||||
|
export default content; |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es5", |
||||||
|
"module": "commonjs", |
||||||
|
"removeComments": true, |
||||||
|
"esModuleInterop": true |
||||||
|
}, |
||||||
|
"include": ["src/server", "src/shared"] |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ESNext", |
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "node", |
||||||
|
"removeComments": true, |
||||||
|
"esModuleInterop": true |
||||||
|
}, |
||||||
|
"include": ["src/web", "src/shared"] |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
const path = require("path"); |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
module: { |
||||||
|
rules: [ |
||||||
|
{ |
||||||
|
exclude: [/node_modules/], |
||||||
|
test: /\.ts$/, |
||||||
|
loader: "ts-loader", |
||||||
|
options: { configFile: "tsconfig.server.json" }, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
output: { |
||||||
|
filename: "server.js", |
||||||
|
path: path.resolve(__dirname, "dist", "server"), |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
extensions: [".ts", ".js"], |
||||||
|
}, |
||||||
|
target: "node", |
||||||
|
}; |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); |
||||||
|
const { merge } = require("webpack-merge"); |
||||||
|
const nodeExternals = require("webpack-node-externals"); |
||||||
|
const path = require("path"); |
||||||
|
const webpack = require("webpack"); |
||||||
|
|
||||||
|
const common = require("./webpack.server.common.js"); |
||||||
|
|
||||||
|
module.exports = merge(common, { |
||||||
|
devtool: "inline-source-map", |
||||||
|
entry: [path.join(__dirname, "src/server/server-dev.ts")], |
||||||
|
externals: [ nodeExternals(), ], |
||||||
|
mode: "development", |
||||||
|
plugins: [new CleanWebpackPlugin()], |
||||||
|
}); |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); |
||||||
|
const { merge } = require("webpack-merge"); |
||||||
|
const nodeExternals = require("webpack-node-externals"); |
||||||
|
const path = require("path"); |
||||||
|
|
||||||
|
const common = require("./webpack.server.common.js"); |
||||||
|
|
||||||
|
module.exports = merge(common, { |
||||||
|
devtool: "source-map", |
||||||
|
node: { __dirname: false }, |
||||||
|
entry: [path.join(__dirname, "src/server/server.ts")], |
||||||
|
externals: [nodeExternals()], |
||||||
|
mode: "production", |
||||||
|
plugins: [new CleanWebpackPlugin()], |
||||||
|
}); |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
const path = require("path"); |
||||||
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); |
||||||
|
const HtmlWebpackPlugin = require("html-webpack-plugin"); |
||||||
|
const CopyWebpackPlugin = require("copy-webpack-plugin"); |
||||||
|
const Dotenv = require("dotenv-webpack"); |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
target: "web", |
||||||
|
module: { |
||||||
|
rules: [ |
||||||
|
{ |
||||||
|
exclude: [/node_modules/], |
||||||
|
test: /\.ts$/, |
||||||
|
loader: "ts-loader", |
||||||
|
options: { configFile: "tsconfig.web.json" }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.(css|scss)$/, |
||||||
|
use: ["css-loader", "sass-loader"], |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.(png|jpg|gif)$/, |
||||||
|
type: "asset/resource", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
output: { |
||||||
|
filename: "bundle.[name].js", |
||||||
|
path: path.resolve(__dirname, "dist", "web"), |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
extensions: [".ts", ".js"], |
||||||
|
}, |
||||||
|
plugins: [ |
||||||
|
new CleanWebpackPlugin(), |
||||||
|
new HtmlWebpackPlugin({ |
||||||
|
template: "./src/index.ejs", |
||||||
|
title: "My App", |
||||||
|
}), |
||||||
|
new CopyWebpackPlugin({ |
||||||
|
patterns: [ |
||||||
|
"./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js", |
||||||
|
{ |
||||||
|
from: "./node_modules/@webcomponents/webcomponentsjs/bundles", |
||||||
|
to: "bundles", |
||||||
|
}, |
||||||
|
], |
||||||
|
}), |
||||||
|
new Dotenv(), |
||||||
|
], |
||||||
|
}; |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
const { merge, } = require("webpack-merge"); |
||||||
|
const webpack = require("webpack"); |
||||||
|
|
||||||
|
const common = require("./webpack.web.common.js"); |
||||||
|
|
||||||
|
module.exports = merge(common, { |
||||||
|
devtool: "inline-source-map", |
||||||
|
entry: ["webpack-hot-middleware/client?timeout=1000&reload=true", "./src/web/main.ts"], |
||||||
|
mode: "development", |
||||||
|
plugins: [ |
||||||
|
new webpack.HotModuleReplacementPlugin(), |
||||||
|
], |
||||||
|
}); |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
const { merge } = require("webpack-merge"); |
||||||
|
const path = require("path"); |
||||||
|
|
||||||
|
const common = require("./webpack.web.common.js"); |
||||||
|
|
||||||
|
module.exports = merge(common, { |
||||||
|
// devtool: "source-map",
|
||||||
|
entry: [path.join(__dirname, "src/web/main.ts")], |
||||||
|
mode: "production", |
||||||
|
}); |
||||||
Loading…
Reference in new issue