commit
fb97dc15f8
23 changed files with 16449 additions and 0 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
.env |
||||
dist |
||||
node_modules |
||||
yarn-error.log |
||||
@ -0,0 +1,23 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
import express from "express"; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
export default router; |
||||
@ -0,0 +1,18 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,9 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "es5", |
||||
"module": "commonjs", |
||||
"removeComments": true, |
||||
"esModuleInterop": true |
||||
}, |
||||
"include": ["src/server", "src/shared"] |
||||
} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "ESNext", |
||||
"module": "ESNext", |
||||
"moduleResolution": "node", |
||||
"removeComments": true, |
||||
"esModuleInterop": true |
||||
}, |
||||
"include": ["src/web", "src/shared"] |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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