mirror of
https://github.com/SinTan1729/chhoto-url
synced 2025-04-19 11:20:00 -05:00
Compare commits
No commits in common. "main" and "5.6.0" have entirely different histories.
23 changed files with 222 additions and 657 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,4 +10,3 @@ urls.sqlite
|
|||
.env
|
||||
cookie*
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
|
2
Makefile
2
Makefile
|
@ -24,7 +24,7 @@ docker-test: docker-local docker-stop
|
|||
docker run -p ${PORT}:${PORT} --name chhoto-url -e password="${PASSWORD}" -e public_mode="${PUBLIC_MODE}" \
|
||||
-e site_url="${SITE_URL}" -e db_url="${DB_URL}" -e redirect_method="${REDIRECT_METHOD}" -e port="${PORT}"\
|
||||
-e slug_style="${SLUG_STYLE}" -e slug_length="${SLUG_LENGTH}" -e cache_control_header="${CACHE_CONTROL_HEADER}"\
|
||||
-e api_key="${API_KEY}" -e disable_frontend="${DISABLE_FRONTEND}"\
|
||||
-e api_key="${API_KEY}"\
|
||||
-d chhoto-url
|
||||
docker logs chhoto-url -f
|
||||
|
||||
|
|
27
README.md
27
README.md
|
@ -45,10 +45,9 @@ for small. URL means, well... URL. So the name simply means Small URL.
|
|||
stays under 5MB under normal use.)
|
||||
- Counts number of hits for each short link in a privacy respecting way
|
||||
i.e. only the hit is recorded, and nothing else.
|
||||
- Has a mobile friendly UI, and automatic dark mode.
|
||||
- Has a mobile friendly UI.
|
||||
- Has a public mode, where anyone can add links without authentication. Deleting
|
||||
or listing available links will need admin access using the password. It's also
|
||||
possible to completely disable the frontend.
|
||||
or listing available links will need admin access using the password.
|
||||
- Allows setting the URL of your website, in case you want to conveniently
|
||||
generate short links locally.
|
||||
- Links are stored in an SQLite database.
|
||||
|
@ -57,7 +56,7 @@ for small. URL means, well... URL. So the name simply means Small URL.
|
|||
written in plain HTML and vanilla JS, using [Pure CSS](https://purecss.io/)
|
||||
for styling.
|
||||
- Uses very basic authentication using a provided password. It's not encrypted in transport.
|
||||
I recommend using a reverse proxy such as [caddy](https://caddyserver.com/) to
|
||||
I recommend using something like [caddy](https://caddyserver.com/) to
|
||||
encrypt the connection by SSL.
|
||||
|
||||
# Bloat that will not be implemented
|
||||
|
@ -73,6 +72,7 @@ not needed here.
|
|||
whatever reason), you can message me through GitHub issues.
|
||||
|
||||
# Screenshots
|
||||
#### Note: I'm using Dark Reader here to get the dark theme.
|
||||
<p align="middle">
|
||||
<img src="screenshot-desktop.webp" height="250" alt="desktop screenshot" />
|
||||
<img src="screenshot-mobile.webp" height="250" alt="mobile screenshot" />
|
||||
|
@ -147,8 +147,7 @@ the `slug_style` variable to `UID`. You can also set the length of those slug by
|
|||
the `slug_length` variable. It defaults to 8, and a minimum of 4 is supported.
|
||||
|
||||
To enable public mode, set `public_mode` to `Enable`. With this, anyone will be able to add
|
||||
links. Listing existing links or deleting links will need admin access using the password. To
|
||||
completely disable the frontend, set `disable_frontend` to `True`.
|
||||
links. Listing existing links or deleting links will need admin access using the password.
|
||||
|
||||
By default, the server sends no Cache-Control headers. You can set custom `cache_control_header`
|
||||
to send your desired headers. It must be a comma separated list of valid
|
||||
|
@ -156,20 +155,6 @@ to send your desired headers. It must be a comma separated list of valid
|
|||
you can set it to `no-cache, private` to disable caching. It might help during testing if
|
||||
served through a proxy.
|
||||
|
||||
## Deploying in your Kubernetes cluster with Helm
|
||||
The helm values are very sparse to keep it simple. If you need more values to be variable, feel free to adjust.
|
||||
|
||||
The PVC allocates 100Mi and the PV is using a host path volume.
|
||||
|
||||
The helm chart assumes you have [cert manager](https://github.com/jetstack/cert-manager) deployed to have TLS certificates managed easily in your cluster. Feel free to remove the issuer and adjust the ingress if you're on AWS with EKS for example. To install cert-manager, I recommend using the ["kubectl apply" way](https://cert-manager.io/docs/installation/kubectl/) to install cert-manager.
|
||||
|
||||
To get started, `cp helm-chart/values.yaml helm-chart/my-values.yaml` and adjust `password`, `fqdn` and `letsencryptmail` in your new `my-values.yaml`, then just run
|
||||
|
||||
``` bash
|
||||
cd helm-chart
|
||||
helm upgrade --install chhoto-url . -n chhoto-url --create-namespace -f my-values.yaml
|
||||
```
|
||||
|
||||
## Instructions for CLI usage
|
||||
The application can be used from the terminal using something like `curl`. In all the examples
|
||||
below, replace `http://localhost:4567` with where your instance of `chhoto-url` is accessible.
|
||||
|
@ -247,7 +232,5 @@ that those links aren't created by you.
|
|||
|
||||
## Notes
|
||||
- It started as a fork of [`simply-shorten`](https://gitlab.com/draganczukp/simply-shorten).
|
||||
- There's an (unofficial) extension maintained by for shortening URLs easily using Chhoto URL.
|
||||
[You can take a look at it here.](https://github.com/SolninjaA/Chhoto-URL-Extension)
|
||||
- The list of adjectives and names used for random short url generation is a modified
|
||||
version of [this list used by docker](https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go).
|
||||
|
|
482
actix/Cargo.lock
generated
482
actix/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@
|
|||
|
||||
[package]
|
||||
name = "chhoto-url"
|
||||
version = "5.6.3"
|
||||
version = "5.6.0"
|
||||
edition = "2021"
|
||||
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
|
||||
license = "mit"
|
||||
|
@ -29,9 +29,9 @@ categories = ["web-programming"]
|
|||
[dependencies]
|
||||
actix-web = "4.5.1"
|
||||
actix-files = "0.6.5"
|
||||
rusqlite = { version = "0.34.0", features = ["bundled"] }
|
||||
rusqlite = { version = "0.32.0", features = ["bundled"] }
|
||||
regex = "1.10.3"
|
||||
rand = "0.9.0"
|
||||
rand = "0.8.5"
|
||||
passwords = "3.1.16"
|
||||
actix-session = { version = "0.10.0", features = ["cookie-session"] }
|
||||
env_logger = "0.11.1"
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn find_url(shortlink: &str, db: &Connection, needhits: bool) -> (Option<Str
|
|||
// Get all URLs in DB
|
||||
pub fn getall(db: &Connection) -> Vec<DBRow> {
|
||||
let mut statement = db
|
||||
.prepare_cached("SELECT * FROM urls ORDER BY id ASC")
|
||||
.prepare_cached("SELECT * FROM urls")
|
||||
.expect("Error preparing SQL statement for getall.");
|
||||
|
||||
let mut data = statement
|
||||
|
|
|
@ -30,7 +30,6 @@ async fn main() -> Result<()> {
|
|||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or(String::from("urls.sqlite"));
|
||||
|
||||
// Get the port environment variable
|
||||
let port = env::var("port")
|
||||
.unwrap_or(String::from("4567"))
|
||||
.parse::<u16>()
|
||||
|
@ -40,51 +39,24 @@ async fn main() -> Result<()> {
|
|||
.ok()
|
||||
.filter(|s| !s.trim().is_empty());
|
||||
|
||||
let disable_frontend = env::var("disable_frontend").is_ok_and(|s| s.trim() == "True");
|
||||
|
||||
// If an API key is set, check the security
|
||||
if let Ok(key) = env::var("api_key") {
|
||||
if !auth::is_key_secure() {
|
||||
eprintln!("WARN: API key is insecure! Please change it. Current key is: {}. Generated secure key which you may use: {}", key, auth::gen_key())
|
||||
eprintln!("API key is insecure! Please change it. Current key is: {}. Generated secure key which you may use: {}", key, auth::gen_key())
|
||||
} else {
|
||||
eprintln!("Secure API key was provided.")
|
||||
}
|
||||
}
|
||||
|
||||
// If the site_url env variable exists
|
||||
if let Some(site_url) = env::var("site_url").ok().filter(|s| !s.trim().is_empty()) {
|
||||
// Get first and last characters of the site_url
|
||||
let mut chars = site_url.chars();
|
||||
let first = chars.next();
|
||||
let last = chars.next_back();
|
||||
let url = chars.as_str();
|
||||
// If the site_url is encapsulated by quotes (i.e. invalid)
|
||||
if first == Option::from('"') || first == Option::from('\'') && first == last {
|
||||
// Set the site_url without the quotes
|
||||
env::set_var("site_url", url);
|
||||
eprintln!("WARN: The site_url environment variable is encapsulated by quotes. Automatically adjusting to {}", url);
|
||||
|
||||
// Tell the user what URI the server will respond with
|
||||
eprintln!("INFO: Public URI is: {url}:{port}.")
|
||||
} else {
|
||||
// No issues
|
||||
eprintln!("INFO: Configured Site URL is: {site_url}.");
|
||||
|
||||
// Tell the user what URI the server will respond with
|
||||
eprintln!("INFO: Public URI is: {site_url}:{port}.")
|
||||
}
|
||||
} else {
|
||||
// Site URL is not configured
|
||||
eprintln!("WARN: The site_url environment variable is not configured. Defaulting to http://localhost");
|
||||
eprintln!("INFO: Public URI is: http://localhost:{port}.")
|
||||
}
|
||||
|
||||
// Tell the user that the server has started, and where it is listening to, rather than simply outputting nothing
|
||||
eprintln!("Server has started at 0.0.0.0 on port {port}.");
|
||||
if let Some(site_url) = env::var("site_url").ok().filter(|s| !s.trim().is_empty()) {
|
||||
eprintln!("Configured Site URL is: {site_url}.");
|
||||
}
|
||||
|
||||
// Actually start the server
|
||||
HttpServer::new(move || {
|
||||
let mut app = App::new()
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(
|
||||
|
@ -110,15 +82,10 @@ async fn main() -> Result<()> {
|
|||
.service(services::delete_link)
|
||||
.service(services::login)
|
||||
.service(services::logout)
|
||||
.service(services::expand);
|
||||
|
||||
if !disable_frontend {
|
||||
app = app.service(Files::new("/", "./resources/").index_file("index.html"));
|
||||
}
|
||||
|
||||
app.default_service(actix_web::web::get().to(services::error404))
|
||||
.service(services::expand)
|
||||
.service(Files::new("/", "./resources/").index_file("index.html"))
|
||||
.default_service(actix_web::web::get().to(services::error404))
|
||||
})
|
||||
// Hardcode the port the server listens to. Allows for more intuitive Docker Compose port management
|
||||
.bind(("0.0.0.0", port))?
|
||||
.run()
|
||||
.await
|
||||
|
|
|
@ -11,6 +11,7 @@ use actix_web::{
|
|||
Either, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use std::env;
|
||||
|
||||
// Serialize JSON data
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -67,7 +68,7 @@ pub async fn add_link(
|
|||
.unwrap_or(String::from("4567"))
|
||||
.parse::<u16>()
|
||||
.expect("Supplied port is not an integer");
|
||||
let mut url = format!(
|
||||
let url = format!(
|
||||
"{}:{}",
|
||||
env::var("site_url")
|
||||
.ok()
|
||||
|
@ -75,22 +76,6 @@ pub async fn add_link(
|
|||
.unwrap_or(String::from("http://localhost")),
|
||||
port
|
||||
);
|
||||
// If the port is 80, remove the port from the returned URL (better for copying and pasting)
|
||||
// Return http://
|
||||
if port == 80 {
|
||||
url = env::var("site_url")
|
||||
.ok()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or(String::from("http://localhost"));
|
||||
}
|
||||
// If the port is 443, remove the port from the returned URL (better for copying and pasting)
|
||||
// Return https://
|
||||
if port == 443 {
|
||||
url = env::var("site_url")
|
||||
.ok()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or(String::from("https://localhost"));
|
||||
}
|
||||
let response = CreatedURL {
|
||||
success: true,
|
||||
error: false,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use crate::{auth, database};
|
||||
use actix_web::HttpRequest;
|
||||
use nanoid::nanoid;
|
||||
use rand::seq::IndexedRandom;
|
||||
use rand::seq::SliceRandom;
|
||||
use regex::Regex;
|
||||
use rusqlite::Connection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -190,10 +190,10 @@ fn gen_link(style: String, len: usize) -> String {
|
|||
format!(
|
||||
"{0}-{1}",
|
||||
ADJECTIVES
|
||||
.choose(&mut rand::rng())
|
||||
.choose(&mut rand::thread_rng())
|
||||
.expect("Error choosing random adjective."),
|
||||
NAMES
|
||||
.choose(&mut rand::rng())
|
||||
.choose(&mut rand::thread_rng())
|
||||
.expect("Error choosing random name.")
|
||||
)
|
||||
}
|
||||
|
|
24
compose.yaml
24
compose.yaml
|
@ -6,16 +6,7 @@ services:
|
|||
image: sintan1729/chhoto-url:latest
|
||||
restart: unless-stopped
|
||||
container_name: chhoto-url
|
||||
# You may enable the next two options if you want, but it may break the program if the db is bind
|
||||
# mounted from the system. It does add extra security, but I don't know enough about docker
|
||||
# to help in case it breaks something.
|
||||
# read_only: true
|
||||
# cap_drop:
|
||||
# - ALL
|
||||
ports:
|
||||
# If you changed the "port" environment variable, adjust accordingly
|
||||
# The number AFTER the colon should match the "port" variable and the number
|
||||
# before the colon is the port where you would access the container from outside.
|
||||
- 4567:4567
|
||||
environment:
|
||||
# Change if you want to mount the database somewhere else.
|
||||
|
@ -27,17 +18,11 @@ services:
|
|||
# a copy of your database.)
|
||||
- db_url=/db/urls.sqlite
|
||||
|
||||
# Change this if your server URL is not "http://localhost"
|
||||
# This must not be surrounded by quotes. For example:
|
||||
# site_url="https://www.example.com" incorrect
|
||||
# site_url=https://www.example.com correct
|
||||
# This is important to ensure Chhoto URL outputs the shortened link with the correct URL.
|
||||
# Change it in case you want to set the website name
|
||||
# displayed in front of the shorturls, defaults to
|
||||
# the hostname you're accessing it from.
|
||||
# - site_url=https://www.example.com
|
||||
|
||||
# Change this if you are running Chhoto URL on a port which is not 4567.
|
||||
# This is important to ensure Chhoto URL outputs the shortened link with the correct port.
|
||||
# - port=4567
|
||||
|
||||
- password=TopSecretPass
|
||||
|
||||
# This needs to be set in order to use programs that use the JSON interface of Chhoto URL.
|
||||
|
@ -59,9 +44,6 @@ services:
|
|||
# In case you want to provide public access to adding links (and not
|
||||
# delete, or listing), change the following option to Enable.
|
||||
# - public_mode=Disable
|
||||
# In case you want to completely disable the frontend, change the following
|
||||
# to True.
|
||||
# - disable_frontend=False
|
||||
|
||||
# By default, the server sends no Cache-Control headers. You can supply a
|
||||
# comma separated list of valid header as per RFC 7234 §5.2 to send those
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
apiVersion: v2
|
||||
name: chhoto-url
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
|
@ -1,23 +0,0 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: chhoto-url
|
||||
annotations:
|
||||
cert-manager.io/issuer: "letsencrypt"
|
||||
acme.cert-manager.io/http01-edit-in-place: "true"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.fqdn }}
|
||||
secretName: my-tls
|
||||
rules:
|
||||
- host: {{ .Values.fqdn }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: chhoto-url
|
||||
port:
|
||||
number: 80
|
|
@ -1,18 +0,0 @@
|
|||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: letsencrypt
|
||||
spec:
|
||||
acme:
|
||||
# The ACME server URL
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
# Email address used for ACME registration
|
||||
email: {{ .Values.letsencryptmail }}
|
||||
# Name of a secret used to store the ACME account private key
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt
|
||||
# Enable the HTTP-01 challenge provider
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
ingressClassName: nginx
|
|
@ -1,13 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: chhoto-pv
|
||||
labels:
|
||||
app: chhoto-url
|
||||
spec:
|
||||
capacity:
|
||||
storage: 100Mi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
hostPath:
|
||||
path: {{ .Values.persistence.hostPath.path }}
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret
|
||||
type: Opaque
|
||||
data:
|
||||
password: {{ .Values.password }}
|
||||
{{- if .Values.api_key }}
|
||||
api_key: {{ .Values.api_key }}
|
||||
{{- end }}
|
|
@ -1,61 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: chhoto-url
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: chhoto-url
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: chhoto-url
|
||||
spec:
|
||||
containers:
|
||||
- name: chhoto-url
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
ports:
|
||||
- containerPort: 4567
|
||||
env:
|
||||
- name: password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: secret
|
||||
key: password
|
||||
{{- if .Values.api_key }}
|
||||
- name: api_key
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: secret
|
||||
key: api_key
|
||||
{{- end }}
|
||||
- name: db_url
|
||||
value: /db/urls.sqlite
|
||||
- name: site_url
|
||||
value: "{{ .Values.protocol }}://{{ .Values.fqdn }}"
|
||||
- name: redirect_method
|
||||
value: {{ .Values.redirect_method }}
|
||||
- name: slug_style
|
||||
value: {{ .Values.slug_style }}
|
||||
- name: slug_length
|
||||
value: "{{ .Values.slug_length }}"
|
||||
- name: public_mode
|
||||
value: {{ .Values.public_mode }}
|
||||
- name: disable_frontend
|
||||
value: {{ .Values.disable_frontend }}
|
||||
{{- if .Values.cache_control_header }}
|
||||
- name: cache_control_header
|
||||
value: {{ .Values.cache_control_header }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /db
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: [ "ReadWriteOnce" ]
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Mi
|
|
@ -1,14 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: chhoto-url
|
||||
labels:
|
||||
app: chhoto-url
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 4567
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: chhoto-url
|
|
@ -1,28 +0,0 @@
|
|||
# Default values for chhoto-url.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
image:
|
||||
repository: sintan1729/chhoto-url
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "5.4.6"
|
||||
|
||||
# please use a better password in your values and base64 encode it
|
||||
password: cGFzc3dvcmQ=
|
||||
# if used, needs to be base64 encoded as well
|
||||
# api_key: U0VDVVJFX0FQSV9LRVk=
|
||||
|
||||
persistence:
|
||||
hostPath:
|
||||
path: /mnt/data/chhoto-data
|
||||
|
||||
redirect_method: PERMANENT
|
||||
slug_style: Pair
|
||||
slug_length: 8
|
||||
public_mode: Disable
|
||||
disable_frontend: False
|
||||
# cache_control_header: "no-cache, private"
|
||||
|
||||
protocol: https
|
||||
fqdn: your.short.link.url.com
|
||||
letsencryptmail: your.mail@address.com
|
|
@ -78,7 +78,7 @@
|
|||
<p>Please enter password to access this website</p>
|
||||
<input type="password" id="password" />
|
||||
<button class="pure-button pure-button-primary" value="default">Log in</button>
|
||||
<p id="wrong-pass" hidden>Wrong password!</p>
|
||||
<p id="wrong-pass"> </p>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ const refreshData = async () => {
|
|||
}
|
||||
} else {
|
||||
let data = await res.json();
|
||||
displayData(data.reverse());
|
||||
displayData(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,10 +129,10 @@ const copyShortUrl = async (link) => {
|
|||
const site = await getSiteUrl();
|
||||
try {
|
||||
navigator.clipboard.writeText(`${site}/${link}`);
|
||||
showAlert(`Short URL ${link} was copied to clipboard!`, "light-dark(green, #72ff72)");
|
||||
showAlert(`Short URL ${link} was copied to clipboard!`, "green");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
showAlert(`Could not copy short URL to clipboard, please do it manually: <a href=${site}/${link}>${site}/${link}</a>`, "light-dark(red, #ff1a1a)");
|
||||
showAlert(`Could not copy short URL to clipboard, please do it manually: <a href=${site}/${link}>${site}/${link}</a>`, "red");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ const submitForm = () => {
|
|||
})
|
||||
.then(text => {
|
||||
if (!ok) {
|
||||
showAlert(text, "light-dark(red, #ff1a1a)");
|
||||
showAlert(text, "red");
|
||||
}
|
||||
else {
|
||||
copyShortUrl(text);
|
||||
|
@ -234,10 +234,11 @@ const submitLogin = () => {
|
|||
document.getElementById("container").style.filter = "blur(0px)"
|
||||
document.getElementById("login-dialog").close();
|
||||
password.value = '';
|
||||
document.getElementById("wrong-pass").hidden = true;
|
||||
refreshData();
|
||||
} else {
|
||||
document.getElementById("wrong-pass").hidden = false;
|
||||
const wrongPassBox = document.getElementById("wrong-pass");
|
||||
wrongPassBox.innerHTML = "Wrong password!";
|
||||
wrongPassBox.style.color = "red";
|
||||
password.focus();
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,33 +6,6 @@
|
|||
src: url('/assets/Montserrat-VF.woff2');
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
color: light-dark(black, #e8e6e3);
|
||||
background-color: light-dark(white, #181a1b);
|
||||
}
|
||||
|
||||
.pure-button {
|
||||
background-color: light-dark(#0078e7, #0060b9);
|
||||
}
|
||||
|
||||
input {
|
||||
border-color: light-dark(#cccccc, #3e4446) !important;
|
||||
box-shadow: light-dark(#dddddd, #2b2f31) 0px 1px 3px inset !important;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: light-dark(#757575, #636061);
|
||||
}
|
||||
|
||||
legend {
|
||||
color: light-dark(#333333, #c8c3bc) !important;
|
||||
border-bottom-color: light-dark(#e5e5e5 ,#373c3e) !important;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Montserrat;
|
||||
}
|
||||
|
@ -42,10 +15,6 @@ legend {
|
|||
margin: 20px auto auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: light-dark(blue, #3391ff);
|
||||
}
|
||||
|
||||
table tr td div {
|
||||
max-height: 75px;
|
||||
line-height: 25px;
|
||||
|
@ -54,19 +23,6 @@ table tr td div {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
.pure-table {
|
||||
border-color: light-dark(black, #867d6e);
|
||||
}
|
||||
|
||||
.pure-table caption {
|
||||
color: light-dark(black, #e8e6e3);
|
||||
}
|
||||
|
||||
.pure-table thead {
|
||||
color: light-dark(black, #e8e6e3);
|
||||
background-color: light-dark(#e0e0e0, #2a2d2f);
|
||||
}
|
||||
|
||||
.pure-table td {
|
||||
border-left: none;
|
||||
}
|
||||
|
@ -130,19 +86,10 @@ div[name="links-div"] {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#login-dialog {
|
||||
border-radius: 10px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
#login-dialog form {
|
||||
dialog form {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#wrong-pass {
|
||||
color: light-dark(red, #ff1a1a);
|
||||
}
|
||||
|
||||
/* Settings for mobile devices */
|
||||
@media (pointer:none),
|
||||
(pointer:coarse) {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB |
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 53 KiB |
Loading…
Reference in a new issue