mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
11 Commits
91eed3638e
...
518056cc75
Author | SHA1 | Date | |
---|---|---|---|
518056cc75 | |||
ee7e9ef2a3 | |||
d120f00fb5 | |||
94d145e8d0 | |||
66143dce20 | |||
d17d37ceb0 | |||
a06b3bb37f | |||
ec39500bbd | |||
9533de5c8c | |||
881cfe026c | |||
f90c190baf |
@ -1,4 +1,5 @@
|
|||||||
node_modules/*
|
node_modules/*
|
||||||
|
public/
|
||||||
Dockerfile
|
Dockerfile
|
||||||
.dockerignore
|
.dockerignore
|
||||||
**/.gitignore
|
**/.gitignore
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
FROM --platform=$BUILDPLATFORM node:lts-alpine as development
|
||||||
|
WORKDIR /opt/app
|
||||||
|
|
||||||
|
RUN apk --no-cache add \
|
||||||
|
dumb-init \
|
||||||
|
nginx \
|
||||||
|
git
|
||||||
|
|
||||||
|
RUN ln -sf /opt/app/nginx.conf.docker /etc/nginx/nginx.conf
|
||||||
|
RUN rm -rf /var/www
|
||||||
|
RUN ln -sf /opt/app/public/ /var/www
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
ARG BUILD_INFO="docker-development"
|
||||||
|
ENV BUILD_INFO=${BUILD_INFO}
|
||||||
|
ENV BACKEND_HOST="server"
|
||||||
|
|
||||||
|
CMD ["/opt/app/docker-start-dev.sh"]
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM node:lts as builder
|
FROM --platform=$BUILDPLATFORM node:lts as builder
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ function makeOutputDirs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function watch() {
|
function watch() {
|
||||||
let wss = new WebSocket.Server({ port: 8080 });
|
let wss = new WebSocket.Server({ port: environment === "development" ? 8081 : 8080 });
|
||||||
const liveReload = !process.argv.includes('--no-live-reload');
|
const liveReload = !process.argv.includes('--no-live-reload');
|
||||||
|
|
||||||
function emitReload() {
|
function emitReload() {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
font-size: 1em
|
font-size: 1em
|
||||||
color: $inactive-link-color
|
color: $inactive-link-color
|
||||||
float: right
|
float: right
|
||||||
line-height: 2em
|
line-height: 2rem
|
||||||
.expander-content
|
.expander-content
|
||||||
padding: 0.5em 0.5em 2em 0.5em
|
padding: 0.5em 0.5em 2em 0.5em
|
||||||
|
|
||||||
|
@ -15,13 +15,22 @@ $cancel-button-color = tomato
|
|||||||
&.inactive .skip-duplicates
|
&.inactive .skip-duplicates
|
||||||
&.inactive .always-upload-similar
|
&.inactive .always-upload-similar
|
||||||
&.inactive .pause-remain-on-error
|
&.inactive .pause-remain-on-error
|
||||||
|
&.inactive .upload-all-anonymous
|
||||||
|
&.inactive .expander
|
||||||
|
&.inactive #common-tags,
|
||||||
&.uploading input[type=submit],
|
&.uploading input[type=submit],
|
||||||
&.uploading .skip-duplicates,
|
&.uploading .skip-duplicates,
|
||||||
&.uploading .always-upload-similar
|
&.uploading .always-upload-similar
|
||||||
&.uploading .pause-remain-on-error
|
&.uploading .pause-remain-on-error
|
||||||
|
&.uploading .upload-all-anonymous
|
||||||
|
&.uploading .expander
|
||||||
&:not(.uploading) .cancel
|
&:not(.uploading) .cancel
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
|
&.inactive .control-strip
|
||||||
|
&.uploading .control-strip
|
||||||
|
gap: 0
|
||||||
|
|
||||||
.dropper-container
|
.dropper-container
|
||||||
margin: 0 auto
|
margin: 0 auto
|
||||||
.file-dropper
|
.file-dropper
|
||||||
@ -30,28 +39,36 @@ $cancel-button-color = tomato
|
|||||||
small
|
small
|
||||||
font-size: 60%
|
font-size: 60%
|
||||||
|
|
||||||
input[type=submit]
|
.expander
|
||||||
margin-top: 1em
|
&.collapsed
|
||||||
|
margin-bottom: 0
|
||||||
|
.expander-content
|
||||||
|
padding: 0.5em
|
||||||
|
|
||||||
.cancel
|
.cancel
|
||||||
margin-top: 1em
|
|
||||||
background: $cancel-button-color
|
background: $cancel-button-color
|
||||||
border-color: $cancel-button-color
|
border-color: $cancel-button-color
|
||||||
&:focus
|
&:focus
|
||||||
border: 2px solid $text-color
|
border: 2px solid $text-color
|
||||||
|
|
||||||
.skip-duplicates
|
|
||||||
margin-left: 1em
|
|
||||||
|
|
||||||
.always-upload-similar
|
|
||||||
margin-left: 1em
|
|
||||||
|
|
||||||
.pause-remain-on-error
|
|
||||||
margin-left: 1em
|
|
||||||
|
|
||||||
form>.messages
|
form>.messages
|
||||||
margin-top: 1em
|
margin-top: 1em
|
||||||
|
|
||||||
|
.control-strip
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1em
|
||||||
|
margin-top: 1em
|
||||||
|
|
||||||
|
.control-options
|
||||||
|
display: flex
|
||||||
|
flex-wrap: wrap
|
||||||
|
gap: 0 1em
|
||||||
|
|
||||||
|
span
|
||||||
|
flex: 1 1 30%
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
.uploadables-container
|
.uploadables-container
|
||||||
list-style-type: none
|
list-style-type: none
|
||||||
margin: 0
|
margin: 0
|
||||||
|
17
client/docker-start-dev.sh
Executable file
17
client/docker-start-dev.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/dumb-init /bin/sh
|
||||||
|
|
||||||
|
# Integrate environment variables
|
||||||
|
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||||
|
/etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
nginx &
|
||||||
|
|
||||||
|
# Watch source for changes and build app
|
||||||
|
# FIXME: It's not ergonomic to run `npm i` outside of the build step.
|
||||||
|
# However, the mounting of different directories into the
|
||||||
|
# client container's /opt/app causes node_modules to disappear
|
||||||
|
# (the mounting causes client/Dockerfile's RUN npm install
|
||||||
|
# to silently clobber).
|
||||||
|
# Find a way to move `npm i` into client/Dockerfile.
|
||||||
|
npm i && npm run watch -- --polling
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
# Integrate environment variables
|
# Integrate environment variables
|
||||||
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||||
/etc/nginx/nginx.conf
|
/etc/nginx/nginx.conf
|
||||||
sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
|
sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
|
||||||
/var/www/index.htm \
|
/var/www/index.htm \
|
||||||
/var/www/manifest.json
|
/var/www/manifest.json
|
||||||
|
|
||||||
# Start server
|
# Start server
|
||||||
exec nginx
|
exec nginx
|
||||||
|
@ -3,32 +3,45 @@
|
|||||||
<div class='dropper-container'></div>
|
<div class='dropper-container'></div>
|
||||||
|
|
||||||
<div class='control-strip'>
|
<div class='control-strip'>
|
||||||
|
<div class='control-options'>
|
||||||
|
<span class='skip-duplicates'>
|
||||||
|
<%= ctx.makeCheckbox({
|
||||||
|
text: 'Skip duplicate',
|
||||||
|
name: 'skip-duplicates',
|
||||||
|
checked: false,
|
||||||
|
}) %>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class='always-upload-similar'>
|
||||||
|
<%= ctx.makeCheckbox({
|
||||||
|
text: 'Force upload similar',
|
||||||
|
name: 'always-upload-similar',
|
||||||
|
checked: false,
|
||||||
|
}) %>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class='pause-remain-on-error'>
|
||||||
|
<%= ctx.makeCheckbox({
|
||||||
|
text: 'Pause on error',
|
||||||
|
name: 'pause-remain-on-error',
|
||||||
|
checked: true,
|
||||||
|
}) %>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class='upload-all-anonymous'>
|
||||||
|
<%= ctx.makeCheckbox({
|
||||||
|
text: 'Upload anonymously',
|
||||||
|
name: 'upload-all-anonymous',
|
||||||
|
checked: false,
|
||||||
|
}) %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class='common-tags'>
|
||||||
|
<%= ctx.makeTextInput({name: 'common-tags'}) %>
|
||||||
|
</section>
|
||||||
|
|
||||||
<input type='submit' value='Upload all' class='submit'/>
|
<input type='submit' value='Upload all' class='submit'/>
|
||||||
|
|
||||||
<span class='skip-duplicates'>
|
|
||||||
<%= ctx.makeCheckbox({
|
|
||||||
text: 'Skip duplicate',
|
|
||||||
name: 'skip-duplicates',
|
|
||||||
checked: false,
|
|
||||||
}) %>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class='always-upload-similar'>
|
|
||||||
<%= ctx.makeCheckbox({
|
|
||||||
text: 'Force upload similar',
|
|
||||||
name: 'always-upload-similar',
|
|
||||||
checked: false,
|
|
||||||
}) %>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class='pause-remain-on-error'>
|
|
||||||
<%= ctx.makeCheckbox({
|
|
||||||
text: 'Pause on error',
|
|
||||||
name: 'pause-remain-on-error',
|
|
||||||
checked: true,
|
|
||||||
}) %>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input type='button' value='Cancel' class='cancel'/>
|
<input type='button' value='Cancel' class='cancel'/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
const config = require("./config.js");
|
const config = require("./config.js");
|
||||||
|
|
||||||
if (config.environment == "development") {
|
if (config.environment == "development") {
|
||||||
var ws = new WebSocket("ws://" + location.hostname + ":8080");
|
var ws = new WebSocket("ws://" + location.hostname + ":8081");
|
||||||
ws.addEventListener("open", function (event) {
|
ws.addEventListener("open", function (event) {
|
||||||
console.log("Live-reloading websocket connected.");
|
console.log("Live-reloading websocket connected.");
|
||||||
});
|
});
|
||||||
|
@ -81,7 +81,7 @@ function makeCheckbox(options) {
|
|||||||
name: options.name,
|
name: options.name,
|
||||||
value: options.value,
|
value: options.value,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
checked: options.checked !== undefined ? options.checked : false,
|
checked: options.checked !== undefined && options.checked !== null ? options.checked : false,
|
||||||
disabled: options.readonly,
|
disabled: options.readonly,
|
||||||
required: options.required,
|
required: options.required,
|
||||||
}),
|
}),
|
||||||
|
@ -4,10 +4,14 @@ const events = require("../events.js");
|
|||||||
const api = require("../api.js");
|
const api = require("../api.js");
|
||||||
const views = require("../util/views.js");
|
const views = require("../util/views.js");
|
||||||
const FileDropperControl = require("../controls/file_dropper_control.js");
|
const FileDropperControl = require("../controls/file_dropper_control.js");
|
||||||
|
const ExpanderControl = require("../controls/expander_control.js");
|
||||||
|
const TagInputControl = require("../controls/tag_input_control.js");
|
||||||
|
|
||||||
const template = views.getTemplate("post-upload");
|
const template = views.getTemplate("post-upload");
|
||||||
const rowTemplate = views.getTemplate("post-upload-row");
|
const rowTemplate = views.getTemplate("post-upload-row");
|
||||||
|
|
||||||
|
const TagList = require("../models/tag_list.js");
|
||||||
|
|
||||||
function _mimeTypeToPostType(mimeType) {
|
function _mimeTypeToPostType(mimeType) {
|
||||||
return (
|
return (
|
||||||
{
|
{
|
||||||
@ -160,6 +164,7 @@ class PostUploadView extends events.EventTarget {
|
|||||||
this._uploadables.find = (u) => {
|
this._uploadables.find = (u) => {
|
||||||
return this._uploadables.findIndex((u2) => u.key === u2.key);
|
return this._uploadables.findIndex((u2) => u.key === u2.key);
|
||||||
};
|
};
|
||||||
|
this._commonTags = new TagList();
|
||||||
|
|
||||||
this._contentFileDropper = new FileDropperControl(
|
this._contentFileDropper = new FileDropperControl(
|
||||||
this._contentInputNode,
|
this._contentInputNode,
|
||||||
@ -185,6 +190,23 @@ class PostUploadView extends events.EventTarget {
|
|||||||
this._evtFormSubmit(e)
|
this._evtFormSubmit(e)
|
||||||
);
|
);
|
||||||
this._formNode.classList.add("inactive");
|
this._formNode.classList.add("inactive");
|
||||||
|
|
||||||
|
this._commonTagsExpander = new ExpanderControl(
|
||||||
|
"common-tags",
|
||||||
|
"Common Tags (0)",
|
||||||
|
this._hostNode.querySelectorAll(".common-tags")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._commonTagsInputNode) {
|
||||||
|
this._commonTagsControl = new TagInputControl(
|
||||||
|
this._commonTagsInputNode,
|
||||||
|
this._commonTags
|
||||||
|
);
|
||||||
|
|
||||||
|
this._commonTagsControl.addEventListener("change", (_) => {
|
||||||
|
this._commonTagsExpander.title = `Common Tags (${this._commonTags.length})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enableForm() {
|
enableForm() {
|
||||||
@ -299,14 +321,16 @@ class PostUploadView extends events.EventTarget {
|
|||||||
uploadable.safety = safetyNode.value;
|
uploadable.safety = safetyNode.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anonymousNode = rowNode.querySelector(
|
let anonymous = this._uploadAllAnonymous.checked;
|
||||||
".anonymous input:checked"
|
if (!anonymous && rowNode.querySelector(".anonymous input:checked")) {
|
||||||
);
|
anonymous = true;
|
||||||
if (anonymousNode) {
|
|
||||||
uploadable.anonymous = true;
|
|
||||||
}
|
}
|
||||||
|
uploadable.anonymous = anonymous;
|
||||||
|
|
||||||
uploadable.tags = [];
|
uploadable.tags = [];
|
||||||
|
if (this._commonTagsInputNode) {
|
||||||
|
uploadable.tags = this._commonTags.map((tag) => tag.names[0]);
|
||||||
|
}
|
||||||
uploadable.relations = [];
|
uploadable.relations = [];
|
||||||
for (let [i, lookalike] of uploadable.lookalikes.entries()) {
|
for (let [i, lookalike] of uploadable.lookalikes.entries()) {
|
||||||
let lookalikeNode = rowNode.querySelector(
|
let lookalikeNode = rowNode.querySelector(
|
||||||
@ -441,6 +465,12 @@ class PostUploadView extends events.EventTarget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _uploadAllAnonymous() {
|
||||||
|
return this._hostNode.querySelector(
|
||||||
|
"form [name=upload-all-anonymous]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get _submitButtonNode() {
|
get _submitButtonNode() {
|
||||||
return this._hostNode.querySelector("form [type=submit]");
|
return this._hostNode.querySelector("form [type=submit]");
|
||||||
}
|
}
|
||||||
@ -452,6 +482,10 @@ class PostUploadView extends events.EventTarget {
|
|||||||
get _contentInputNode() {
|
get _contentInputNode() {
|
||||||
return this._formNode.querySelector(".dropper-container");
|
return this._formNode.querySelector(".dropper-container");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _commonTagsInputNode() {
|
||||||
|
return this._formNode.querySelector(".common-tags input");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PostUploadView;
|
module.exports = PostUploadView;
|
||||||
|
54
docker-compose.dev.yml
Normal file
54
docker-compose.dev.yml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
## Docker Compose configuration for dev iteration
|
||||||
|
##
|
||||||
|
## Data is transient by using named vols.
|
||||||
|
## Run: docker-compose -f ./docker-compose.dev.yml up
|
||||||
|
services:
|
||||||
|
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: ./server
|
||||||
|
target: development
|
||||||
|
depends_on:
|
||||||
|
- sql
|
||||||
|
environment:
|
||||||
|
## These should be the names of the dependent containers listed below,
|
||||||
|
## or FQDNs/IP addresses if these services are running outside of Docker
|
||||||
|
POSTGRES_HOST: sql
|
||||||
|
## Credentials for database:
|
||||||
|
POSTGRES_USER:
|
||||||
|
POSTGRES_PASSWORD:
|
||||||
|
## Commented Values are Default:
|
||||||
|
#POSTGRES_DB: defaults to same as POSTGRES_USER
|
||||||
|
#POSTGRES_PORT: 5432
|
||||||
|
#LOG_SQL: 0 (1 for verbose SQL logs)
|
||||||
|
THREADS:
|
||||||
|
volumes:
|
||||||
|
- "data:/data"
|
||||||
|
- "./server/:/opt/app/"
|
||||||
|
|
||||||
|
client:
|
||||||
|
build:
|
||||||
|
context: ./client
|
||||||
|
target: development
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
volumes:
|
||||||
|
- "data:/data:ro"
|
||||||
|
- "./client/:/opt/app/"
|
||||||
|
- "/opt/app/public/"
|
||||||
|
ports:
|
||||||
|
- "${PORT}:80"
|
||||||
|
- "8081:8081"
|
||||||
|
|
||||||
|
sql:
|
||||||
|
image: postgres:11-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER:
|
||||||
|
POSTGRES_PASSWORD:
|
||||||
|
volumes:
|
||||||
|
- "sql:/var/lib/postgresql/data"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
sql:
|
@ -61,7 +61,42 @@ ENTRYPOINT ["pytest", "--tb=short"]
|
|||||||
CMD ["szurubooru/"]
|
CMD ["szurubooru/"]
|
||||||
|
|
||||||
|
|
||||||
|
FROM prereqs as development
|
||||||
|
WORKDIR /opt/app
|
||||||
|
|
||||||
|
ARG PUID=1000
|
||||||
|
ARG PGID=1000
|
||||||
|
|
||||||
|
RUN apk --no-cache add \
|
||||||
|
dumb-init \
|
||||||
|
py3-pip \
|
||||||
|
py3-setuptools \
|
||||||
|
py3-waitress \
|
||||||
|
&& pip3 install --no-cache-dir --disable-pip-version-check \
|
||||||
|
hupper \
|
||||||
|
&& mkdir -p /opt/app /data \
|
||||||
|
&& addgroup -g ${PGID} app \
|
||||||
|
&& adduser -SDH -h /opt/app -g '' -G app -u ${PUID} app \
|
||||||
|
&& chown -R app:app /opt/app /data
|
||||||
|
|
||||||
|
USER app
|
||||||
|
CMD ["/opt/app/docker-start-dev.sh"]
|
||||||
|
|
||||||
|
ARG PORT=6666
|
||||||
|
ENV PORT=${PORT}
|
||||||
|
EXPOSE ${PORT}
|
||||||
|
|
||||||
|
ARG THREADS=4
|
||||||
|
ENV THREADS=${THREADS}
|
||||||
|
|
||||||
|
VOLUME ["/data/"]
|
||||||
|
|
||||||
|
|
||||||
FROM prereqs as release
|
FROM prereqs as release
|
||||||
|
|
||||||
|
COPY ./ /opt/app/
|
||||||
|
RUN rm -rf /opt/app/szurubooru/tests
|
||||||
|
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
|
|
||||||
ARG PUID=1000
|
ARG PUID=1000
|
||||||
|
8
server/docker-start-dev.sh
Executable file
8
server/docker-start-dev.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/dumb-init /bin/sh
|
||||||
|
set -e
|
||||||
|
cd /opt/app
|
||||||
|
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
echo "Starting szurubooru API on port ${PORT} - Running on ${THREADS} threads"
|
||||||
|
exec hupper -m waitress --port ${PORT} --threads ${THREADS} szurubooru.facade:app
|
Reference in New Issue
Block a user