Ready to go?

This commit is contained in:
Jordan Roher 2023-04-19 22:36:24 -07:00
parent 0712ea6a71
commit ed1252a668
11 changed files with 257 additions and 506 deletions

5
.gitignore vendored
View File

@ -19,7 +19,4 @@ dist-ssr
*.ntvs*
*.njsproj
*.sln
*.sw?
# Starbase 80
config
*.sw?

230
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,25 @@
{
"name": "sb80",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"serve": "vite preview"
},
"dependencies": {
"@types/react-helmet": "^6.1.6",
"js-yaml": "^4.1.0",
"prettier": "^2.8.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-helmet": "^6.1.6",
"@types/react": "^18.0.28",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.14",
"js-yaml": "^4.1.0",
"postcss": "^8.4.23",
"prettier": "^2.8.7",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react": "^18.2.0",
"tailwindcss": "^3.3.1",
"typescript": "^4.9.3",
"vite": "^4.2.0"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
public/icons/router.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,146 +0,0 @@
[
{
"name": "Archivebox",
"uri": "https://archivebox.starbase80.dev",
"description": "Backup webpages",
"icon": {
"href": "/icons/archivebox.jpg"
}
},
{
"name": "Authelia",
"uri": "https://auth.starbase80.dev",
"description": "Authentication",
"icon": {
"href": "/icons/authelia.png"
}
},
{
"name": "Calibre",
"uri": "https://calibre.starbase80.dev",
"description": "eBook library",
"icon": {
"href": "/icons/calibre.png"
}
},
{
"name": "Gitea",
"uri": "https://git.starbase80.dev",
"description": "Code hosting",
"icon": {
"href": "/icons/gitea.svg"
}
},
{
"name": "Jellyfin",
"uri": "https://jellyfin.starbase80.dev",
"description": "Media server",
"icon": {
"href": "/icons/jellyfin.svg"
}
},
{
"name": "Mastodon",
"uri": "https://notclickable.social",
"description": "NotClickable.social",
"icon": {
"href": "/icons/mastodon.jpg"
}
},
{
"name": "Mealie",
"uri": "https://mealie.starbase80.dev",
"description": "Recipe manager",
"icon": {
"href": "/icons/mealie.jpg"
}
},
{
"name": "N8N",
"uri": "https://n8n.starbase80.dev",
"description": "Workflow automation",
"icon": {
"href": "/icons/n8n.jpg"
}
},
{
"name": "MeTube",
"uri": "https://metube.starbase80.dev",
"description": "Archive YouTube",
"icon": {
"href": "/icons/metube.jpg"
}
},
{
"name": "Miniflux",
"uri": "https://miniflux.starbase80.dev",
"description": "RSS server",
"icon": {
"href": "/icons/miniflux.jpg"
}
},
{
"name": "Portainer",
"uri": "https://portainer.starbase80.dev",
"description": "Docker management",
"icon": {
"href": "/icons/portainer.png"
}
},
{
"name": "Roher Twins",
"uri": "https://rohertwins.starbase80.dev",
"description": "Weekly newsletter",
"icon": {
"href": "/icons/ghost.jpg"
}
},
{
"name": "Roher Wiki",
"uri": "https://roherwiki.starbase80.dev",
"description": "Family wiki",
"icon": {
"href": "/icons/wikijs.svg"
}
},
{
"name": "Standard Notes",
"uri": "https://standardnotes.starbase80.dev",
"description": "Knowledge base",
"icon": {
"href": "/icons/standardnotes.png"
}
},
{
"name": "Vaultwarden",
"uri": "https://vaultwarden.starbase80.dev",
"description": "Password manager",
"icon": {
"href": "/icons/vaultwarden.svg"
}
},
{
"name": "Visual Studio Code",
"uri": "https://vscode.starbase80.dev",
"description": "Code editor",
"icon": {
"href": "/icons/vscode.jpg"
}
},
{
"name": "Wallabag",
"uri": "https://wallabag.starbase80.dev",
"description": "Read later",
"icon": {
"href": "/icons/wallabag.png"
}
},
{
"name": "Woodpecker",
"uri": "https://woodpecker.starbase80.dev",
"description": "Continuous integration",
"icon": {
"href": "/icons/woodpecker.jpg"
}
}
]

View File

@ -0,0 +1,20 @@
import React from "react";
import { IServiceCatalog } from "../shared/types";
import { Services } from "./services";
interface IProps {
catalogs: IServiceCatalog[];
}
export const ServiceCatalogs: React.FunctionComponent<IProps> = ({ catalogs }) => {
return (
<ul className="">
{catalogs.map((catalog, index) => (
<li key={index} className="mt-10 first:mt-0">
<h2 className="text-2xl text-slate-800 inline-block py-2 px-4">{catalog.category}</h2>
<Services services={catalog.services} />
</li>
))}
</ul>
);
};

177
src/config/services.json Normal file
View File

@ -0,0 +1,177 @@
[
{
"category": "Services",
"services": [
{
"name": "Archivebox",
"uri": "https://archivebox.starbase80.dev",
"description": "Backup webpages",
"icon": {
"href": "/icons/archivebox.jpg"
}
},
{
"name": "Authelia",
"uri": "https://auth.starbase80.dev",
"description": "Authentication",
"icon": {
"href": "/icons/authelia.png"
}
},
{
"name": "Calibre",
"uri": "https://calibre.starbase80.dev",
"description": "eBook library",
"icon": {
"href": "/icons/calibre.png"
}
},
{
"name": "Gitea",
"uri": "https://git.starbase80.dev",
"description": "Code hosting",
"icon": {
"href": "/icons/gitea.svg"
}
},
{
"name": "Jellyfin",
"uri": "https://jellyfin.starbase80.dev",
"description": "Media server",
"icon": {
"href": "/icons/jellyfin.svg"
}
},
{
"name": "Mastodon",
"uri": "https://notclickable.social",
"description": "NotClickable.social",
"icon": {
"href": "/icons/mastodon.jpg"
}
},
{
"name": "Mealie",
"uri": "https://mealie.starbase80.dev",
"description": "Recipe manager",
"icon": {
"href": "/icons/mealie.jpg"
}
},
{
"name": "N8N",
"uri": "https://n8n.starbase80.dev",
"description": "Workflow automation",
"icon": {
"href": "/icons/n8n.jpg"
}
},
{
"name": "MeTube",
"uri": "https://metube.starbase80.dev",
"description": "Archive YouTube",
"icon": {
"href": "/icons/metube.jpg"
}
},
{
"name": "Miniflux",
"uri": "https://miniflux.starbase80.dev",
"description": "RSS server",
"icon": {
"href": "/icons/miniflux.jpg"
}
},
{
"name": "Portainer",
"uri": "https://portainer.starbase80.dev",
"description": "Docker management",
"icon": {
"href": "/icons/portainer.png"
}
},
{
"name": "Roher Twins",
"uri": "https://rohertwins.starbase80.dev",
"description": "Weekly newsletter",
"icon": {
"href": "/icons/ghost.jpg"
}
},
{
"name": "Roher Wiki",
"uri": "https://roherwiki.starbase80.dev",
"description": "Family wiki",
"icon": {
"href": "/icons/wikijs.svg"
}
},
{
"name": "Standard Notes",
"uri": "https://standardnotes.starbase80.dev",
"description": "Knowledge base",
"icon": {
"href": "/icons/standardnotes.png"
}
},
{
"name": "Vaultwarden",
"uri": "https://vaultwarden.starbase80.dev",
"description": "Password manager",
"icon": {
"href": "/icons/vaultwarden.svg"
}
},
{
"name": "Visual Studio Code",
"uri": "https://vscode.starbase80.dev",
"description": "Code editor",
"icon": {
"href": "/icons/vscode.jpg"
}
},
{
"name": "Wallabag",
"uri": "https://wallabag.starbase80.dev",
"description": "Read later",
"icon": {
"href": "/icons/wallabag.png"
}
},
{
"name": "Woodpecker",
"uri": "https://woodpecker.starbase80.dev",
"description": "Continuous integration",
"icon": {
"href": "/icons/woodpecker.jpg"
}
}
]
},
{
"category": "Devices",
"services": [
{
"name": "Router",
"uri": "http://192.168.1.1/",
"description": "Netgear Orbi",
"icon": {
"href": "/icons/router.png"
}
},
{
"name": "Home Assistant",
"uri": "http://homeassistant.local:8123/",
"description": "Home automation",
"icon": {
"href": "/icons/home-assistant.svg"
}
},
{
"name": "Synology",
"uri": "http://synology:5000",
"description": "Network storage"
}
]
}
]

View File

@ -1,8 +1,8 @@
import React from "react";
import userServices from "../../public/services.json";
import { Header } from "../components/header";
import { Services } from "../components/services";
import { IService } from "../shared/types";
import { ServiceCatalogs } from "../components/service-catalogs";
import userServices from "../config/services.json";
import { IServiceCatalog } from "../shared/types";
interface IProps {
title?: string;
@ -10,7 +10,7 @@ interface IProps {
}
export const IndexPage: React.FunctionComponent<IProps> = ({ icon, title }) => {
const mySerices = userServices as IService[];
const mySerices = userServices as IServiceCatalog[];
return (
<div className="min-h-screen flex flex-col xl:flex-row">
@ -18,7 +18,7 @@ export const IndexPage: React.FunctionComponent<IProps> = ({ icon, title }) => {
<Header title={title} icon={icon} />
</div>
<div className="min-h-screen p-4 flex-grow">
<Services services={mySerices} />
<ServiceCatalogs catalogs={mySerices} />
</div>
</div>
);

View File

@ -15,147 +15,6 @@ function IsNull(data: any): boolean {
);
}
function IsDate(data: any): boolean {
return data instanceof Date;
}
function IsDateString(data: any): boolean {
return !IsNull(data) && data.indexOf("/Date(") === 0;
}
function IsJsonString(data: any): boolean {
try {
JSON.parse(data);
return true;
} catch {
return false;
}
}
function IsNumeric(data: any): boolean {
return (
typeof data !== "undefined" &&
data !== null &&
data.toString().length > 0 &&
data.toString().replace(/[\d.]+/gi, "").length === 0 &&
!isNaN(data)
);
}
function IsString(data: any): boolean {
return !IsNull(data) && typeof data === "string";
}
function IsValidHttpUrl(data: any): boolean {
// Copyright (c) 2010-2013 Diego Perini, MIT licensed
// https://gist.github.com/dperini/729294
// see also https://mathiasbynens.be/demo/url-regex
// modified to allow protocol-relative URLs
// and modified again to remove ftp (Jordan)
if (IsNull(data)) {
return false;
}
const validUrlRegex =
/^(?:(?:(?:https?):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
return validUrlRegex.test(data);
}
function IsFunction(data: any): boolean {
return !IsNull(data) && data instanceof Function;
}
const IsReactRoute = (uri: string): boolean => {
const reactRouteRegex = /[a-z]{1,4}-[a-z]{1,4}\/r\/[ts]\//gi;
return !IsNull(uri) && reactRouteRegex.test(uri);
};
const IsArrayDifferent = (array1: any[], array2: any[], useDeepComparison = false): boolean => {
const isArray1Null = IsNull(array1);
const isArray2Null = IsNull(array2);
if (isArray1Null && isArray2Null) {
return false;
}
const shallowResult =
isArray1Null !== isArray2Null || (!isArray1Null && !isArray2Null && array1.length !== array2.length);
if (shallowResult) {
return true;
}
if (!useDeepComparison) {
return array1 !== array2;
}
return array1.some((array1Item, index) => {
return array2[index] !== array1Item;
});
};
const IsInArray = (array: any[], element: any): boolean => {
return !IsNull(array) && array.indexOf(element) !== -1;
};
const IsShallowEqual = (obj1: any, obj2: any, ignorePrivateProperties: boolean = false): boolean => {
if (is.null(obj1) && is.null(obj2)) {
return true;
} else if (is.null(obj1) !== is.null(obj2)) {
return false;
} else if (typeof obj1 !== "object" && typeof obj2 !== "object") {
return obj1 === obj2;
}
for (const p in obj1) {
if (ignorePrivateProperties && p.startsWith("_")) {
continue;
}
if (obj1.hasOwnProperty(p)) {
if (obj1[p] !== obj2[p]) {
return false;
}
}
}
for (const p in obj2) {
if (ignorePrivateProperties && p.startsWith("_")) {
continue;
}
if (obj2.hasOwnProperty(p)) {
if (obj1[p] !== obj2[p]) {
return false;
}
}
}
return true;
};
const IsAnythingDifferent = (props: any, nextProps: any, ...keys: string[]): boolean => {
return keys.some(key => {
if (IsArray(props[key])) {
return IsArrayDifferent(props[key], nextProps[key], true);
}
return props[key] !== nextProps[key];
});
};
export const is = {
array: IsArray,
arrayDifferent: IsArrayDifferent,
null: IsNull,
date: IsDate,
dateString: IsDateString,
numeric: IsNumeric,
string: IsString,
validHttpUrl: IsValidHttpUrl,
function: IsFunction,
reactRoute: IsReactRoute,
jsonString: IsJsonString,
inArray: IsInArray,
shallowEqual: IsShallowEqual,
anythingDifferent: IsAnythingDifferent,
};

View File

@ -1,3 +1,8 @@
export interface IServiceCatalog {
category: string;
services: IService[];
}
export interface IService {
name: string;
uri: string;