Enables developers to extend the existing config and API. Initial proof of concept and work in progress. Implementation details may change. Feedback welcome! Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
18473e4d44
commit
e739dd3e89
|
@ -39,7 +39,9 @@ var log = event.Log
|
|||
|
||||
const appName = "PhotoPrism"
|
||||
const appEdition = "PhotoPrism® CE"
|
||||
const appDescription = "AI-Powered Photos App. Visit https://docs.photoprism.app/ to learn more."
|
||||
const appDescription = "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web." +
|
||||
" It makes use of the latest technologies to tag and find pictures automatically without getting in your way." +
|
||||
" You can run it at home, on a private server, or in the cloud."
|
||||
const appCopyright = "(c) 2018-2022 PhotoPrism UG. All rights reserved."
|
||||
|
||||
// Metadata contains build specific information.
|
||||
|
|
|
@ -29,6 +29,7 @@ import themes from "options/themes.json";
|
|||
import translations from "locales/translations.json";
|
||||
import { Languages } from "options/options";
|
||||
import { Photo } from "model/photo";
|
||||
import { onSetTheme } from "common/hooks";
|
||||
|
||||
export default class Config {
|
||||
/**
|
||||
|
@ -340,13 +341,22 @@ export default class Config {
|
|||
}
|
||||
|
||||
setTheme(name) {
|
||||
this.themeName = name;
|
||||
let theme = onSetTheme(name, this);
|
||||
|
||||
if (!theme) {
|
||||
this.themeName = name;
|
||||
theme = themes[name] ? themes[name] : themes["default"];
|
||||
}
|
||||
|
||||
if (this.values.settings && this.values.settings.ui) {
|
||||
this.values.settings.ui.theme = this.themeName;
|
||||
}
|
||||
|
||||
Event.publish("view.refresh", this);
|
||||
|
||||
this.theme = themes[name] ? themes[name] : themes["default"];
|
||||
this.theme = theme;
|
||||
|
||||
this.setBodyTheme(name);
|
||||
this.setBodyTheme(this.themeName);
|
||||
|
||||
if (this.theme.dark) {
|
||||
this.setColorMode("dark");
|
||||
|
|
3
frontend/src/common/hooks.js
Normal file
3
frontend/src/common/hooks.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function onSetTheme(/*name, config*/) {
|
||||
return false;
|
||||
}
|
|
@ -4,14 +4,14 @@
|
|||
<v-layout wrap align-top pt-3>
|
||||
<v-flex xs12 sm6 class="px-0 pb-2 body-1 text-selectable text-xs-left">
|
||||
<strong><router-link to="/about" class="text-link">{{ $config.getEdition() }}</router-link></strong>
|
||||
<span class="body-link text-selectable">Build <a href="https://docs.photoprism.app/release-notes/" target="_blank" :title="version">{{ build }}</a></span>
|
||||
<span class="body-link text-selectable">Build <a href="https://docs.photoprism.app/release-notes/" target="_blank" :title="version" class="body-link">{{ build }}</a></span>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 class="px-0 pb-2 body-1 text-xs-center text-sm-right">
|
||||
<div class="hidden-xs-only">
|
||||
<a href="https://raw.githubusercontent.com/photoprism/photoprism/develop/NOTICE"
|
||||
target="_blank" class="text-link">3rd-party software packages</a>
|
||||
<a href="https://photoprism.app/team/" target="_blank">© 2018-2022 PhotoPrism UG</a>
|
||||
<a href="https://photoprism.app/team/" target="_blank" class="body-link">© 2018-2022 PhotoPrism UG</a>
|
||||
</div>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
|
|
|
@ -13,11 +13,15 @@
|
|||
width: 190px;
|
||||
height: 30vh;
|
||||
max-height: 190px;
|
||||
margin: 32px auto 16px auto;
|
||||
margin: 20px auto 40px auto;
|
||||
position: relative;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
#photoprism .auth-login .action-buttons {
|
||||
margin: 30px auto 20px auto;
|
||||
}
|
||||
|
||||
#photoprism main .auth-login .logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ export const Languages = () => [
|
|||
},
|
||||
];
|
||||
|
||||
export const Themes = () => [
|
||||
let themes = [
|
||||
{
|
||||
text: $gettext("Default"),
|
||||
value: "default",
|
||||
|
@ -281,6 +281,12 @@ export const Themes = () => [
|
|||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const Themes = () => themes;
|
||||
export const SetThemes = (v) => {
|
||||
themes = v;
|
||||
};
|
||||
|
||||
export const MapsAnimate = () => [
|
||||
{
|
||||
text: $gettext("None"),
|
||||
|
|
|
@ -12,42 +12,62 @@
|
|||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-container fluid class="px-4 pt-4 pb-1">
|
||||
<p class="body-2 lh-17">
|
||||
<translate>Our mission is to provide the most user- and privacy-friendly solution to keep your pictures organized and accessible.</translate>
|
||||
<a href="https://link.photoprism.app/roadmap" target="_blank">
|
||||
<translate>The roadmap shows what tasks are in progress, what needs testing, and which feature requests are going to be implemented next.</translate>
|
||||
<p class="body-2">
|
||||
<translate>PhotoPrism® is an AI-Powered Photos App for the Decentralized Web.</translate>
|
||||
<translate>It makes use of the latest technologies to tag and find pictures automatically without getting in your way.</translate>
|
||||
<translate>You can run it at home, on a private server, or in the cloud.</translate>
|
||||
</p>
|
||||
|
||||
<p class="body-1 pb-1">
|
||||
<span v-if="sponsor">
|
||||
<translate>Your continued support helps us provide regular updates and remain independent, so we can fulfill our mission and protect your privacy.</translate>
|
||||
</span>
|
||||
<span v-else>
|
||||
<translate>Sponsors get access to additional features, receive direct technical support via email, and can join our private chat room on matrix.org.</translate>
|
||||
</span>
|
||||
<translate>Being 100% self-funded and independent, we can promise you that we will never sell your data and that we will always be transparent about our software and services.</translate>
|
||||
</p>
|
||||
|
||||
<div v-if="!sponsor">
|
||||
<p class="text-xs-center my-4">
|
||||
<v-btn
|
||||
href="https://photoprism.app/membership"
|
||||
target="_blank"
|
||||
color="primary-button"
|
||||
class="white--text px-3 py-2 action-sponsor"
|
||||
round depressed small
|
||||
>
|
||||
<translate>Become a sponsor</translate>
|
||||
<v-icon :left="rtl" :right="!rtl" size="16" class="ml-2" dark>star</v-icon>
|
||||
</v-btn>
|
||||
</p>
|
||||
|
||||
<p class="body-1 pt-2">
|
||||
<a target="_blank" href="https://link.photoprism.app/github">
|
||||
<translate>Also, please leave a star on GitHub if you like this project. It provides additional motivation to keep going.</translate>
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p v-if="!sponsor" class="text-xs-center my-4">
|
||||
<v-btn
|
||||
href="https://photoprism.app/membership"
|
||||
target="_blank"
|
||||
color="primary-button"
|
||||
class="white--text px-3 py-2 action-sponsor"
|
||||
round depressed small
|
||||
>
|
||||
<translate>Become a sponsor</translate>
|
||||
<v-icon :left="rtl" :right="!rtl" size="16" class="ml-2" dark>star</v-icon>
|
||||
</v-btn>
|
||||
<h3 class="subheading py-2">User Guide</h3>
|
||||
<p>
|
||||
<translate>Visit docs.photoprism.app/user-guide to learn how to sync, organize, and share your pictures.</translate>
|
||||
<translate>Our User Guide also covers many advanced topics, such as migrating from Google Photos and thumbnail quality settings.</translate>
|
||||
<translate>Common issues can be quickly diagnosed and solved using the troubleshooting checklists we provide.</translate>
|
||||
</p>
|
||||
<p><a href="https://docs.photoprism.app/user-guide/" class="text-link" target="_blank">Read the docs ›</a></p>
|
||||
|
||||
<p class="body-1">
|
||||
<translate>Your continued support helps us provide regular updates and services like world maps.</translate>
|
||||
<translate>Sponsors get access to additional features, receive direct technical support via email, and can join our private chat room on matrix.org.</translate>
|
||||
</p>
|
||||
<p v-if="!sponsor" class="body-1 pb-2">
|
||||
<a target="_blank" href="https://link.photoprism.app/github">
|
||||
<translate>Also, please leave a star on GitHub if you like this project. It provides additional motivation to keep going.</translate>
|
||||
</a>
|
||||
</p>
|
||||
<h2 class="mt-4 mb-2 body-2 lh-17">
|
||||
<h3 class="subheading py-2">Knowledge Base</h3>
|
||||
<p>Browse the Knowledge Base for detailed information on specific product features, services, and related resources.</p>
|
||||
<p><a href="https://photoprism.app/kb" class="text-link" target="_blank">Learn more ›</a></p>
|
||||
|
||||
<h3 class="subheading py-2">
|
||||
<translate>Getting Support</translate>
|
||||
</h2>
|
||||
</h3>
|
||||
<p class="body-1">
|
||||
<a target="_blank" href="https://docs.photoprism.app/getting-started/troubleshooting/">
|
||||
<translate>Before submitting a support request, please use our Troubleshooting Checklists to determine the cause of your problem.</translate>
|
||||
<translate>If this doesn't help, or you have other questions:</translate>
|
||||
<translate>Before submitting a support request, please use our Troubleshooting Checklists to determine the cause of your problem.</translate>
|
||||
<translate>If this doesn't help, or you have other questions:</translate>
|
||||
</a>
|
||||
</p>
|
||||
<ul class="body-1 mb-3">
|
||||
|
@ -55,12 +75,11 @@
|
|||
<li><a target="_blank" href="https://link.photoprism.app/discussions"><translate>post your question in GitHub Discussions</translate></a></li>
|
||||
<li><a target="_blank" href="https://link.photoprism.app/chat"><translate>or ask in our Community Chat</translate></a></li>
|
||||
</ul>
|
||||
<p class="body-1">
|
||||
<p class="body-1 pb-2">
|
||||
<a target="_blank" href="https://photoprism.app/contact"><translate>In addition, sponsors receive direct technical support via email.</translate></a>
|
||||
</p>
|
||||
|
||||
<p v-if="!sponsor" class="body-1 pb-2">
|
||||
<translate>We'll do our best to answer all your questions. In return, we ask you to back us on Patreon or GitHub Sponsors.</translate>
|
||||
<span v-if="!sponsor">
|
||||
<translate>We'll do our best to answer all your questions. In return, we ask you to back us on Patreon or GitHub Sponsors.</translate>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p v-if="sponsor" class="text-xs-center">
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<template>
|
||||
<div class="p-page p-page-support">
|
||||
<v-toolbar flat color="secondary" :dense="$vuetify.breakpoint.smAndDown">
|
||||
<v-toolbar-title v-if="sent">
|
||||
<translate>Your message has been sent</translate>
|
||||
</v-toolbar-title>
|
||||
<v-toolbar-title v-else>
|
||||
<v-toolbar-title>
|
||||
<translate>Contact Us</translate>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
|
@ -13,7 +10,16 @@
|
|||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-container v-if="sent" fluid class="pa-4">
|
||||
<p class="body-1">We'll get back to you as soon as possible!</p>
|
||||
<h3 class="title font-weight-bold pt-4 pb-2 text-xs-center">
|
||||
<translate>We appreciate your feedback!</translate>
|
||||
</h3>
|
||||
<p class="body-2 py-4 text-xs-center">
|
||||
<translate>Due to the high volume of emails we receive, our team may be unable to get back to you immediately.</translate>
|
||||
<translate>We do our best to respond within five business days or less.</translate>
|
||||
</p>
|
||||
<p class="mt-4 text-xs-center">
|
||||
<img src="https://cdn.photoprism.app/thank-you/colorful.png" width="100%" alt="THANK YOU">
|
||||
</p>
|
||||
</v-container>
|
||||
<v-form v-else ref="form" v-model="valid"
|
||||
autocomplete="off"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<v-flex xs12 sm8 md4 xl3 xxl2>
|
||||
<v-form ref="form" dense class="auth-login-form" accept-charset="UTF-8" @submit.prevent="login">
|
||||
<v-card class="elevation-12 auth-login-box blur-7">
|
||||
<v-card-text class="pa-3">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="logo text-xs-center">
|
||||
<img :src="$config.getIcon()" :alt="config.name">
|
||||
</div>
|
||||
|
@ -43,10 +43,10 @@
|
|||
@keyup.enter.native="login"
|
||||
></v-text-field>
|
||||
<v-spacer></v-spacer>
|
||||
<div class="pa-3 text-xs-center">
|
||||
<div class="action-buttons text-xs-center">
|
||||
<!-- a href="#" target="_blank" class="text-link px-2" :style="`color: ${colors.link}!important`"><translate>Forgot password?</translate></a -->
|
||||
<v-btn :color="colors.primary" round :disabled="loginDisabled"
|
||||
class="white--text action-confirm px-3" @click.stop="login">
|
||||
<v-btn :color="colors.primary" depressed :disabled="loginDisabled"
|
||||
class="white--text action-confirm px-4" @click.stop="login">
|
||||
<translate>Sign in</translate>
|
||||
<v-icon :right="!rtl" :left="rtl" dark>arrow_forward</v-icon>
|
||||
</v-btn>
|
||||
|
@ -58,15 +58,18 @@
|
|||
</v-layout>
|
||||
<footer v-if="sponsor">
|
||||
<v-layout wrap align-top pa-0 ma-0>
|
||||
<v-flex xs12 class="pa-0 body-2 text-selectable text-xs-center white--text"
|
||||
:class="[config.imprint ? 'text-sm-left sm6' : '']">
|
||||
<strong>{{ config.siteCaption ? config.siteCaption : config.siteTitle }}</strong>
|
||||
<v-flex xs12 class="pa-0 body-2 text-selectable text-xs-center white--tex text-sm-left sm6">
|
||||
{{ $config.getEdition() }}
|
||||
</v-flex>
|
||||
|
||||
<v-flex v-if="config.imprint" xs12 sm6 class="pa-0 body-2 text-xs-center text-sm-right white--text">
|
||||
<a v-if="config.imprintUrl" :href="config.imprintUrl" target="_blank" class="text-link"
|
||||
:style="`color: ${colors.link}!important`">{{ config.imprint }}</a>
|
||||
<span v-else>{{ config.imprint }}</span>
|
||||
</v-flex>
|
||||
<v-flex v-else xs12 class="pa-0 body-2 text-selectable text-xs-center white--text text-sm-right sm6">
|
||||
<strong>{{ config.siteCaption ? config.siteCaption : config.siteTitle }}</strong>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</footer>
|
||||
<footer v-else>
|
||||
|
|
10
go.mod
10
go.mod
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e // indirect
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/esimov/pigo v1.4.5
|
||||
github.com/gin-contrib/gzip v0.0.5
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
|
@ -43,7 +43,7 @@ require (
|
|||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/sevlyar/go-daemon v0.1.5
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/studio-b12/gowebdav v0.0.0-20211106090535-29e74efa701f
|
||||
github.com/tensorflow/tensorflow v1.15.2
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
|
@ -51,7 +51,7 @@ require (
|
|||
github.com/urfave/cli v1.22.9
|
||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898
|
||||
gonum.org/v1/gonum v0.11.0
|
||||
gopkg.in/photoprism/go-tz.v2 v2.1.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
@ -70,7 +70,7 @@ require (
|
|||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -82,7 +82,7 @@ require (
|
|||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/goccy/go-json v0.9.7 // indirect
|
||||
github.com/goccy/go-json v0.9.8 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
|
|
31
go.sum
31
go.sum
|
@ -92,11 +92,10 @@ github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg=
|
|||
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
|
||||
github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
|
@ -117,13 +116,10 @@ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
|
|||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
|
@ -132,8 +128,9 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
|||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20220206211657-0a94163c4677 h1:+k/R5MXzpgWkdqHjiuirfHk6QzzTToFxlKVrvkSR/ek=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20220206211657-0a94163c4677/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
|
||||
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
|
@ -194,7 +191,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
@ -218,7 +214,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leandro-lugaresi/hub v1.1.1 h1:zqp0HzFvj4HtqjMBXM2QF17o6PNmR8MJOChgeKl/aw8=
|
||||
github.com/leandro-lugaresi/hub v1.1.1/go.mod h1:XEFWanhHv6Rt3XlteHMxuNDYi8dJcpJjodpqkU+BtIo=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914FOauSkBM=
|
||||
|
@ -236,7 +231,6 @@ github.com/mandykoh/prism v0.35.0/go.mod h1:8l+gpXl2w4aHUtgp9SEv3PFDb0OsrwMyEJUP
|
|||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
@ -249,7 +243,6 @@ github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW
|
|||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
|
||||
|
@ -303,8 +296,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
|
||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20211106090535-29e74efa701f h1:SLJx0nHhb2ZLlYNMAbrYsjwmVwXx4yRT48lNIxOp7ts=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20211106090535-29e74efa701f/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||
github.com/tensorflow/tensorflow v1.15.2 h1:7/f/A664Tml/nRJg04+p3StcrsT53mkcvmxYHXI21Qo=
|
||||
|
@ -315,10 +308,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
|
||||
|
@ -417,8 +408,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -445,7 +436,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -461,8 +451,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -566,7 +556,6 @@ gopkg.in/photoprism/go-tz.v2 v2.1.1 h1:XdNAQRneJmJdXDFovXJbf5eewp3zsir+jJ1Bxdmbn
|
|||
gopkg.in/photoprism/go-tz.v2 v2.1.1/go.mod h1:E1aQvLJs3YA4wbrPMOdX4YEx1TgRO2PLSxnO+J1Kqiw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
|
@ -31,7 +31,6 @@ var UsersCommand = cli.Command{
|
|||
Name: "add",
|
||||
Usage: "Adds a new user",
|
||||
Action: usersAddAction,
|
||||
Hidden: !config.Sponsor(),
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "fullname, n",
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/list"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -29,6 +30,11 @@ func (f CliFlag) Fields() reflect.Value {
|
|||
return fields
|
||||
}
|
||||
|
||||
// Default returns the default value.
|
||||
func (f CliFlag) Default() string {
|
||||
return f.Flag.GetValue()
|
||||
}
|
||||
|
||||
// Hidden checks if the flag is hidden.
|
||||
func (f CliFlag) Hidden() bool {
|
||||
field := f.Fields().FieldByName("Hidden")
|
||||
|
@ -56,6 +62,12 @@ func (f CliFlag) Name() string {
|
|||
return f.Flag.GetName()
|
||||
}
|
||||
|
||||
// CommandFlag returns the full command flag based on the name.
|
||||
func (f CliFlag) CommandFlag() string {
|
||||
n := strings.Split(f.Name(), ",")
|
||||
return "--" + n[0]
|
||||
}
|
||||
|
||||
// Usage returns the command flag usage.
|
||||
func (f CliFlag) Usage() string {
|
||||
if list.Contains(f.Tags, EnvSponsor) {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/pkg/list"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/list"
|
||||
)
|
||||
|
||||
// CliFlags represents a list of command-line parameters.
|
||||
|
@ -10,14 +11,13 @@ type CliFlags []CliFlag
|
|||
|
||||
// Cli returns the currently active command-line parameters.
|
||||
func (f CliFlags) Cli() (result []cli.Flag) {
|
||||
var tags []string
|
||||
result = make([]cli.Flag, 0, len(f))
|
||||
|
||||
switch {
|
||||
case Sponsor():
|
||||
tags = []string{EnvSponsor}
|
||||
for _, flag := range f {
|
||||
result = append(result, flag.Flag)
|
||||
}
|
||||
|
||||
return f.Find(tags)
|
||||
return result
|
||||
}
|
||||
|
||||
// Find finds command-line parameters based on a list of tags.
|
||||
|
@ -34,3 +34,49 @@ func (f CliFlags) Find(tags []string) (result []cli.Flag) {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// Remove removes command flags by name.
|
||||
func (f CliFlags) Remove(names []string) (result CliFlags) {
|
||||
result = make(CliFlags, 0, len(f))
|
||||
|
||||
for _, flag := range f {
|
||||
if list.Contains(names, flag.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, flag)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Insert inserts command flags, if possible after name.
|
||||
func (f CliFlags) Insert(name string, insert []CliFlag) (result CliFlags) {
|
||||
result = make(CliFlags, 0, len(f)+len(insert))
|
||||
|
||||
done := false
|
||||
|
||||
for _, flag := range f {
|
||||
result = append(result, flag)
|
||||
|
||||
if !done && flag.Name() == name {
|
||||
result = append(result, insert...)
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
result = append(result, insert...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Prepend adds command flags at the beginning.
|
||||
func (f CliFlags) Prepend(el []CliFlag) (result CliFlags) {
|
||||
result = make(CliFlags, 0, len(f)+len(el))
|
||||
|
||||
result = append(result, el...)
|
||||
return append(result, f...)
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package config
|
|||
|
||||
// Report returns global config values as a table for reporting.
|
||||
func (f CliFlags) Report() (rows [][]string, cols []string) {
|
||||
cols = []string{"Variable", "Flag", "Usage"}
|
||||
cols = []string{"Environment", "CLI Flag", "Default", "Description"}
|
||||
|
||||
rows = make([][]string, 0, len(f))
|
||||
|
||||
|
@ -11,8 +11,7 @@ func (f CliFlags) Report() (rows [][]string, cols []string) {
|
|||
continue
|
||||
}
|
||||
|
||||
row := []string{flag.EnvVar(), flag.Name(), flag.Usage()}
|
||||
rows = append(rows, row)
|
||||
rows = append(rows, []string{flag.EnvVar(), flag.CommandFlag(), flag.Default(), flag.Usage()})
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
|
|
|
@ -12,6 +12,14 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
type ClientType string
|
||||
|
||||
const (
|
||||
ClientPublic ClientType = "public"
|
||||
ClientGuest ClientType = "guest"
|
||||
ClientUser ClientType = "user"
|
||||
)
|
||||
|
||||
// ClientConfig represents HTTP client / Web UI config options.
|
||||
type ClientConfig struct {
|
||||
Mode string `json:"mode"`
|
||||
|
@ -69,6 +77,7 @@ type ClientConfig struct {
|
|||
Categories CategoryLabels `json:"categories"`
|
||||
Clip int `json:"clip"`
|
||||
Server env.Resources `json:"server"`
|
||||
Ext Values `json:"ext"`
|
||||
}
|
||||
|
||||
// Years represents a list of years.
|
||||
|
@ -249,6 +258,7 @@ func (c *Config) PublicConfig() ClientConfig {
|
|||
Clip: txt.ClipDefault,
|
||||
PreviewToken: "public",
|
||||
DownloadToken: "public",
|
||||
Ext: ClientExt(c, ClientPublic),
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -325,6 +335,7 @@ func (c *Config) GuestConfig() ClientConfig {
|
|||
PreviewToken: c.PreviewToken(),
|
||||
ManifestUri: c.ClientManifestUri(),
|
||||
Clip: txt.ClipDefault,
|
||||
Ext: ClientExt(c, ClientGuest),
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -395,6 +406,7 @@ func (c *Config) UserConfig() ClientConfig {
|
|||
ManifestUri: c.ClientManifestUri(),
|
||||
Clip: txt.ClipDefault,
|
||||
Server: env.Info(),
|
||||
Ext: ClientExt(c, ClientUser),
|
||||
}
|
||||
|
||||
c.Db().
|
||||
|
|
13
internal/config/client_ext.go
Normal file
13
internal/config/client_ext.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package config
|
||||
|
||||
// ClientExt returns optional client config values by namespace.
|
||||
func ClientExt(c *Config, t ClientType) Values {
|
||||
configs := Extensions()
|
||||
result := make(Values, len(configs))
|
||||
|
||||
for _, conf := range configs {
|
||||
result[conf.name] = conf.clientValues(c, t)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -109,6 +109,15 @@ func NewConfig(ctx *cli.Context) *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize package extensions.
|
||||
for _, ext := range Extensions() {
|
||||
if err := ext.init(c); err != nil {
|
||||
log.Warnf("config: failed to initialize extension %s (%s)", clean.Log(ext.name), err)
|
||||
} else {
|
||||
log.Debugf("config: extension %s initialized", clean.Log(ext.name))
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -409,7 +418,7 @@ func (c *Config) SiteDescription() string {
|
|||
|
||||
// SitePreview returns the site preview image URL for sharing.
|
||||
func (c *Config) SitePreview() string {
|
||||
if c.options.SitePreview == "" {
|
||||
if c.options.SitePreview == "" || c.NoSponsor() {
|
||||
return c.SiteUrl() + "static/img/preview.jpg"
|
||||
}
|
||||
|
||||
|
@ -462,9 +471,9 @@ func (c *Config) Demo() bool {
|
|||
return c.options.Demo
|
||||
}
|
||||
|
||||
// Sponsor reports if your continuous support helps to pay for development and operating expenses.
|
||||
// Sponsor reports if you support our mission, see https://photoprism.app/membership.
|
||||
func (c *Config) Sponsor() bool {
|
||||
return c.options.Sponsor || c.Test()
|
||||
return Sponsor || c.options.Sponsor
|
||||
}
|
||||
|
||||
// NoSponsor reports if the instance is not operated by a sponsor.
|
||||
|
@ -515,9 +524,14 @@ func (c *Config) AdminPassword() string {
|
|||
return c.options.AdminPassword
|
||||
}
|
||||
|
||||
// Auth checks if authentication is always required.
|
||||
// AuthMode returns the authentication mode.
|
||||
func (c *Config) AuthMode() string {
|
||||
return strings.ToLower(strings.TrimSpace(c.options.AuthMode))
|
||||
}
|
||||
|
||||
// Auth checks if authentication is required.
|
||||
func (c *Config) Auth() bool {
|
||||
return c.options.Auth
|
||||
return c.AuthMode() != ""
|
||||
}
|
||||
|
||||
// LogLevel returns the Logrus log level.
|
||||
|
@ -673,6 +687,10 @@ func (c *Config) OriginalsLimitBytes() int64 {
|
|||
|
||||
// ResolutionLimit returns the maximum resolution of originals in megapixels (width x height).
|
||||
func (c *Config) ResolutionLimit() int {
|
||||
if c.NoSponsor() {
|
||||
return 100
|
||||
}
|
||||
|
||||
result := c.options.ResolutionLimit
|
||||
|
||||
if result <= 0 {
|
||||
|
|
|
@ -62,16 +62,14 @@ func (c *Config) AppName() string {
|
|||
name = c.SiteTitle()
|
||||
}
|
||||
|
||||
clean := func(r rune) rune {
|
||||
name = strings.Map(func(r rune) rune {
|
||||
switch r {
|
||||
case '\'', '"':
|
||||
return -1
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
name = strings.Map(clean, name)
|
||||
}, name)
|
||||
|
||||
return txt.Clip(name, 32)
|
||||
}
|
||||
|
@ -109,7 +107,7 @@ func (c *Config) WallpaperUri() string {
|
|||
|
||||
// Valid URI? Local file?
|
||||
if p := clean.Path(c.options.WallpaperUri); p == "" {
|
||||
return ""
|
||||
c.options.WallpaperUri = ""
|
||||
} else if fs.FileExists(filepath.Join(c.StaticPath(), assetPath, p)) {
|
||||
c.options.WallpaperUri = path.Join(c.StaticUri(), assetPath, p)
|
||||
} else {
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestConfig_DefaultTheme(t *testing.T) {
|
|||
assert.Equal(t, "grayscale", c.DefaultTheme())
|
||||
c.options.Sponsor = false
|
||||
c.options.Test = true
|
||||
assert.Equal(t, "grayscale", c.DefaultTheme())
|
||||
assert.Equal(t, "default", c.DefaultTheme())
|
||||
c.options.Sponsor = false
|
||||
c.options.Test = false
|
||||
assert.Equal(t, "default", c.DefaultTheme())
|
||||
|
@ -96,9 +96,15 @@ func TestConfig_WallpaperUri(t *testing.T) {
|
|||
c.options.WallpaperUri = "https://cdn.photoprism.app/wallpaper/welcome.jpg"
|
||||
assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri())
|
||||
c.options.Test = false
|
||||
assert.Equal(t, "", c.WallpaperUri())
|
||||
assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri())
|
||||
c.options.Test = true
|
||||
assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri())
|
||||
c.options.Sponsor = false
|
||||
assert.Equal(t, "", c.WallpaperUri())
|
||||
c.options.Sponsor = true
|
||||
assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri())
|
||||
c.options.WallpaperUri = "kashmir"
|
||||
assert.Equal(t, "/static/img/wallpaper/kashmir.jpg", c.WallpaperUri())
|
||||
c.options.WallpaperUri = ""
|
||||
assert.Equal(t, "", c.WallpaperUri())
|
||||
}
|
||||
|
|
34
internal/config/config_ext.go
Normal file
34
internal/config/config_ext.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
extMutex sync.Mutex
|
||||
extensions atomic.Value
|
||||
)
|
||||
|
||||
// Extension represents a named package extension with callbacks.
|
||||
type Extension struct {
|
||||
name string
|
||||
init func(c *Config) error
|
||||
clientValues func(c *Config, t ClientType) Values
|
||||
}
|
||||
|
||||
// Register registers a new package extension.
|
||||
func Register(name string, initConfig func(c *Config) error, clientConfig func(c *Config, t ClientType) Values) {
|
||||
extMutex.Lock()
|
||||
n, _ := extensions.Load().([]Extension)
|
||||
extensions.Store(append(n, Extension{name, initConfig, clientConfig}))
|
||||
extMutex.Unlock()
|
||||
}
|
||||
|
||||
// Extensions returns all registered package extensions.
|
||||
func Extensions() (ext []Extension) {
|
||||
extMutex.Lock()
|
||||
ext, _ = extensions.Load().([]Extension)
|
||||
extMutex.Unlock()
|
||||
return ext
|
||||
}
|
|
@ -31,7 +31,7 @@ func (c *Config) FaceOverlap() int {
|
|||
|
||||
// FaceClusterSize returns the size threshold for faces forming a cluster in pixels.
|
||||
func (c *Config) FaceClusterSize() int {
|
||||
if c.options.FaceClusterSize < 20 || c.options.FaceClusterSize > 10000 {
|
||||
if c.NoSponsor() || c.options.FaceClusterSize < 20 || c.options.FaceClusterSize > 10000 {
|
||||
return face.ClusterSizeThreshold
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ func (c *Config) FaceClusterSize() int {
|
|||
|
||||
// FaceClusterScore returns the quality threshold for faces forming a cluster.
|
||||
func (c *Config) FaceClusterScore() int {
|
||||
if c.options.FaceClusterScore < 1 || c.options.FaceClusterScore > 100 {
|
||||
if c.NoSponsor() || c.options.FaceClusterScore < 1 || c.options.FaceClusterScore > 100 {
|
||||
return face.ClusterScoreThreshold
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func (c *Config) FaceClusterScore() int {
|
|||
|
||||
// FaceClusterCore returns the number of faces forming a cluster core.
|
||||
func (c *Config) FaceClusterCore() int {
|
||||
if c.options.FaceClusterCore < 1 || c.options.FaceClusterCore > 100 {
|
||||
if c.NoSponsor() || c.options.FaceClusterCore < 1 || c.options.FaceClusterCore > 100 {
|
||||
return face.ClusterCore
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ func (c *Config) FaceClusterCore() int {
|
|||
|
||||
// FaceClusterDist returns the radius of faces forming a cluster core.
|
||||
func (c *Config) FaceClusterDist() float64 {
|
||||
if c.options.FaceClusterDist < 0.1 || c.options.FaceClusterDist > 1.5 {
|
||||
if c.NoSponsor() || c.options.FaceClusterDist < 0.1 || c.options.FaceClusterDist > 1.5 {
|
||||
return face.ClusterDist
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ func (c *Config) FaceClusterDist() float64 {
|
|||
|
||||
// FaceMatchDist returns the offset distance when matching faces with clusters.
|
||||
func (c *Config) FaceMatchDist() float64 {
|
||||
if c.options.FaceMatchDist < 0.1 || c.options.FaceMatchDist > 1.5 {
|
||||
if c.NoSponsor() || c.options.FaceMatchDist < 0.1 || c.options.FaceMatchDist > 1.5 {
|
||||
return face.MatchDist
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package config
|
||||
|
||||
// Sponsor checks if sponsor features should be enabled.
|
||||
func Sponsor() bool {
|
||||
return Env(EnvDemo, EnvSponsor, EnvTest)
|
||||
}
|
||||
var Sponsor = Env(EnvDemo, EnvSponsor, EnvTest)
|
||||
|
||||
// DisableWebDAV checks if the built-in WebDAV server should be disabled.
|
||||
func (c *Config) DisableWebDAV() bool {
|
||||
|
|
|
@ -14,6 +14,13 @@ func (c *Config) FFmpegEnabled() bool {
|
|||
|
||||
// FFmpegEncoder returns the FFmpeg AVC encoder name.
|
||||
func (c *Config) FFmpegEncoder() ffmpeg.AvcEncoder {
|
||||
if c.options.FFmpegEncoder == "" || c.options.FFmpegEncoder == ffmpeg.SoftwareEncoder.String() {
|
||||
return ffmpeg.SoftwareEncoder
|
||||
} else if c.NoSponsor() {
|
||||
log.Infof("ffmpeg: hardware transcoding is available to sponsors only")
|
||||
return ffmpeg.SoftwareEncoder
|
||||
}
|
||||
|
||||
return ffmpeg.FindEncoder(c.options.FFmpegEncoder)
|
||||
}
|
||||
|
||||
|
|
|
@ -362,6 +362,15 @@ func (c *Config) AssetsPath() string {
|
|||
return fs.Abs(c.options.AssetsPath)
|
||||
}
|
||||
|
||||
// CustomAssetsPath returns the path to custom assets such as icons, models and translations.
|
||||
func (c *Config) CustomAssetsPath() string {
|
||||
if c.options.CustomAssetsPath != "" {
|
||||
return fs.Abs(c.options.CustomAssetsPath)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// LocalesPath returns the translation locales path.
|
||||
func (c *Config) LocalesPath() string {
|
||||
return filepath.Join(c.AssetsPath(), "locales")
|
||||
|
|
|
@ -167,5 +167,9 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"log-filename", c.LogFilename()},
|
||||
}
|
||||
|
||||
if p := c.CustomAssetsPath(); p != "" {
|
||||
rows = append(rows, []string{"custom-assets-path", p})
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
@ -53,9 +54,45 @@ func (c *Config) TemplatesPath() string {
|
|||
return filepath.Join(c.AssetsPath(), "templates")
|
||||
}
|
||||
|
||||
// CustomTemplatesPath returns the path to custom templates.
|
||||
func (c *Config) CustomTemplatesPath() string {
|
||||
if p := c.CustomAssetsPath(); p != "" {
|
||||
return filepath.Join(p, "templates")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// TemplateFiles returns the file paths of all templates found.
|
||||
func (c *Config) TemplateFiles() []string {
|
||||
results := make([]string, 0, 32)
|
||||
|
||||
tmplPaths := []string{c.TemplatesPath(), c.CustomTemplatesPath()}
|
||||
|
||||
for _, p := range tmplPaths {
|
||||
matches, err := filepath.Glob(regexp.QuoteMeta(p) + "/[A-Za-z0-9]*.*")
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tmplName := range matches {
|
||||
results = append(results, tmplName)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// TemplateExists checks if a template with the given name exists (e.g. index.tmpl).
|
||||
func (c *Config) TemplateExists(name string) bool {
|
||||
return fs.FileExists(filepath.Join(c.TemplatesPath(), name))
|
||||
if found := fs.FileExists(filepath.Join(c.TemplatesPath(), name)); found {
|
||||
return true
|
||||
} else if p := c.CustomTemplatesPath(); p != "" {
|
||||
return fs.FileExists(filepath.Join(p, name))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateName returns the name of the default template (e.g. index.tmpl).
|
||||
|
|
|
@ -177,6 +177,12 @@ func TestConfig_AssetsPath(t *testing.T) {
|
|||
assert.True(t, strings.HasSuffix(c.AssetsPath(), "/assets"))
|
||||
}
|
||||
|
||||
func TestConfig_CustomAssetsPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
assert.Equal(t, "", c.CustomAssetsPath())
|
||||
}
|
||||
|
||||
func TestConfig_DetectNSFW(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
@ -224,6 +230,21 @@ func TestConfig_TemplatesPath(t *testing.T) {
|
|||
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/assets/templates", path)
|
||||
}
|
||||
|
||||
func TestConfig_CustomTemplatesPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
path := c.CustomTemplatesPath()
|
||||
assert.Equal(t, "", path)
|
||||
}
|
||||
|
||||
func TestConfig_TemplatesFiles(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
files := c.TemplateFiles()
|
||||
|
||||
t.Logf("TemplateFiles: %#v", files)
|
||||
}
|
||||
|
||||
func TestConfig_StaticPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
@ -357,6 +378,10 @@ func TestConfig_ResolutionLimit(t *testing.T) {
|
|||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
c.options.ResolutionLimit = -1
|
||||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
c.options.Sponsor = false
|
||||
assert.Equal(t, 100, c.ResolutionLimit())
|
||||
c.options.Sponsor = true
|
||||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
}
|
||||
|
||||
func TestConfig_BaseUri(t *testing.T) {
|
||||
|
|
|
@ -19,19 +19,12 @@ var Flags = CliFlags{
|
|||
Name: "admin-password, pw",
|
||||
Usage: fmt.Sprintf("initial admin `PASSWORD`, must have at least %d characters", entity.PasswordLength),
|
||||
EnvVar: "PHOTOPRISM_ADMIN_PASSWORD",
|
||||
},
|
||||
},
|
||||
CliFlag{
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "public, p",
|
||||
Usage: "disable password authentication, WebDAV, and the advanced settings page",
|
||||
EnvVar: "PHOTOPRISM_PUBLIC",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "auth, a",
|
||||
Usage: "always require password authentication, overrides the public flag if it is set",
|
||||
EnvVar: "PHOTOPRISM_AUTH",
|
||||
Name: "public",
|
||||
Usage: "disable password authentication, incl WebDAV and Advanced Settings",
|
||||
EnvVar: "PHOTOPRISM_PUBLIC",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.StringFlag{
|
||||
|
@ -39,8 +32,7 @@ var Flags = CliFlags{
|
|||
Usage: "log message verbosity `LEVEL` (trace, debug, info, warning, error, fatal, panic)",
|
||||
Value: "info",
|
||||
EnvVar: "PHOTOPRISM_LOG_LEVEL",
|
||||
},
|
||||
},
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "debug",
|
||||
|
@ -416,7 +408,8 @@ var Flags = CliFlags{
|
|||
Usage: "site `CAPTION`",
|
||||
Value: "AI-Powered Photos App",
|
||||
EnvVar: "PHOTOPRISM_SITE_CAPTION",
|
||||
}},
|
||||
},
|
||||
},
|
||||
CliFlag{
|
||||
Flag: cli.StringFlag{
|
||||
Name: "site-description",
|
||||
|
|
|
@ -28,7 +28,7 @@ type Options struct {
|
|||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Trace bool `yaml:"Trace" json:"Trace" flag:"Trace"`
|
||||
Auth bool `yaml:"Auth" json:"-" flag:"auth"`
|
||||
AuthMode string `yaml:"AuthMode" json:"-" flag:"auth-mode"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Unsafe bool `yaml:"-" json:"-" flag:"unsafe"`
|
||||
|
@ -47,6 +47,7 @@ type Options struct {
|
|||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
|
||||
CustomAssetsPath string `yaml:"-" json:"-" flag:"custom-assets-path"`
|
||||
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`
|
||||
Workers int `yaml:"Workers" json:"Workers" flag:"workers"`
|
||||
WakeupInterval time.Duration `yaml:"WakeupInterval" json:"WakeupInterval" flag:"wakeup-interval"`
|
||||
|
|
|
@ -45,7 +45,7 @@ var PkgNameRegexp = regexp.MustCompile("[^a-zA-Z\\-_]+")
|
|||
func NewTestOptions(pkg string) *Options {
|
||||
assetsPath := fs.Abs("../../assets")
|
||||
storagePath := fs.Abs("../../storage")
|
||||
testDataPath := filepath.Join(storagePath, "testdata")
|
||||
dataPath := filepath.Join(storagePath, "testdata")
|
||||
|
||||
pkg = PkgNameRegexp.ReplaceAllString(pkg, "")
|
||||
driver := os.Getenv("PHOTOPRISM_TEST_DRIVER")
|
||||
|
@ -79,7 +79,8 @@ func NewTestOptions(pkg string) *Options {
|
|||
Version: "0.0.0",
|
||||
Copyright: "(c) 2018-2022 PhotoPrism UG. All rights reserved.",
|
||||
Public: true,
|
||||
Auth: false,
|
||||
Sponsor: true,
|
||||
AuthMode: "",
|
||||
Test: true,
|
||||
Debug: true,
|
||||
Trace: false,
|
||||
|
@ -91,13 +92,13 @@ func NewTestOptions(pkg string) *Options {
|
|||
AssetsPath: assetsPath,
|
||||
AutoIndex: -1,
|
||||
AutoImport: 7200,
|
||||
StoragePath: testDataPath,
|
||||
CachePath: testDataPath + "/cache",
|
||||
OriginalsPath: testDataPath + "/originals",
|
||||
ImportPath: testDataPath + "/import",
|
||||
TempPath: testDataPath + "/temp",
|
||||
ConfigPath: testDataPath + "/config",
|
||||
SidecarPath: testDataPath + "/sidecar",
|
||||
StoragePath: dataPath,
|
||||
CachePath: dataPath + "/cache",
|
||||
OriginalsPath: dataPath + "/originals",
|
||||
ImportPath: dataPath + "/import",
|
||||
TempPath: dataPath + "/temp",
|
||||
ConfigPath: dataPath + "/config",
|
||||
SidecarPath: dataPath + "/sidecar",
|
||||
DatabaseDriver: driver,
|
||||
DatabaseDsn: dsn,
|
||||
AdminPassword: "photoprism",
|
||||
|
@ -200,9 +201,10 @@ func CliTestContext() *cli.Context {
|
|||
globalSet.String("darktable-cli", config.DarktableBin, "doc")
|
||||
globalSet.String("darktable-blacklist", config.DarktableBlacklist, "doc")
|
||||
globalSet.String("wakeup-interval", "1h34m9s", "doc")
|
||||
globalSet.Bool("test", true, "doc")
|
||||
globalSet.Bool("debug", false, "doc")
|
||||
globalSet.Bool("detect-nsfw", config.DetectNSFW, "doc")
|
||||
globalSet.Bool("debug", false, "doc")
|
||||
globalSet.Bool("sponsor", true, "doc")
|
||||
globalSet.Bool("test", true, "doc")
|
||||
globalSet.Int("auto-index", config.AutoIndex, "doc")
|
||||
globalSet.Int("auto-import", config.AutoImport, "doc")
|
||||
|
||||
|
@ -225,6 +227,8 @@ func CliTestContext() *cli.Context {
|
|||
LogError(c.Set("darktable-blacklist", "raf,cr3"))
|
||||
LogError(c.Set("wakeup-interval", "1h34m9s"))
|
||||
LogError(c.Set("detect-nsfw", "true"))
|
||||
LogError(c.Set("debug", "false"))
|
||||
LogError(c.Set("sponsor", "true"))
|
||||
LogError(c.Set("test", "true"))
|
||||
LogError(c.Set("auto-index", strconv.Itoa(config.AutoIndex)))
|
||||
LogError(c.Set("auto-import", strconv.Itoa(config.AutoImport)))
|
||||
|
|
4
internal/config/values.go
Normal file
4
internal/config/values.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package config
|
||||
|
||||
// Values is a shortcut for map[string]interface{}
|
||||
type Values map[string]interface{}
|
|
@ -31,7 +31,7 @@ func Logger() gin.HandlerFunc {
|
|||
}
|
||||
|
||||
// Use debug level to keep production logs clean.
|
||||
log.Debugf("http: %s %s (%3d) [%v]",
|
||||
log.Debugf("server: %s %s (%3d) [%v]",
|
||||
method,
|
||||
clean.Log(path),
|
||||
statusCode,
|
||||
|
|
|
@ -25,7 +25,7 @@ func Recovery() gin.HandlerFunc {
|
|||
if err := recover(); err != nil {
|
||||
stack := stack(3)
|
||||
req, _ := httputil.DumpRequest(c.Request, false)
|
||||
log.Debugf("http: %s (%s)\n%s", err, string(req), stack)
|
||||
log.Debugf("server: %s (%s)\n%s", err, string(req), stack)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||
|
@ -192,6 +194,15 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize package extensions.
|
||||
for _, ext := range Extensions() {
|
||||
if err := ext.init(router, conf); err != nil {
|
||||
log.Warnf("server: failed to initialize extension %s (%s)", clean.Log(ext.name), err)
|
||||
} else {
|
||||
log.Debugf("server: extension %s initialized", clean.Log(ext.name))
|
||||
}
|
||||
}
|
||||
|
||||
// Default HTML page for client-side rendering and routing via VueJS.
|
||||
router.NoRoute(func(c *gin.Context) {
|
||||
signUp := gin.H{"message": config.MsgSponsor, "url": config.SignUpURL}
|
||||
|
|
|
@ -82,7 +82,7 @@ func (s *security) process(w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
if !isGoodHost {
|
||||
s.opt.BadHostHandler.ServeHTTP(w, r)
|
||||
return fmt.Errorf("http: bad host %s", clean.Log(r.Host))
|
||||
return fmt.Errorf("server: bad host %s", clean.Log(r.Host))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ func (s *security) process(w http.ResponseWriter, r *http.Request) error {
|
|||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), status)
|
||||
return fmt.Errorf("http: https redirect")
|
||||
return fmt.Errorf("server: https redirect")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
@ -55,7 +56,7 @@ func Start(ctx context.Context, conf *config.Config) {
|
|||
// Enable HTTP compression?
|
||||
switch conf.HttpCompression() {
|
||||
case "gzip":
|
||||
log.Infof("http: enabling gzip compression")
|
||||
log.Infof("server: enabling gzip compression")
|
||||
router.Use(gzip.Gzip(
|
||||
gzip.DefaultCompression,
|
||||
gzip.WithExcludedPaths([]string{
|
||||
|
@ -68,8 +69,8 @@ func Start(ctx context.Context, conf *config.Config) {
|
|||
})))
|
||||
}
|
||||
|
||||
// Set template directory
|
||||
router.LoadHTMLGlob(conf.TemplatesPath() + "/*")
|
||||
// Find and load templates.
|
||||
router.LoadHTMLFiles(conf.TemplateFiles()...)
|
||||
|
||||
// Register HTTP route handlers.
|
||||
registerRoutes(router, conf)
|
||||
|
@ -80,26 +81,26 @@ func Start(ctx context.Context, conf *config.Config) {
|
|||
Handler: router,
|
||||
}
|
||||
|
||||
log.Debugf("http: successfully initialized [%s]", time.Since(start))
|
||||
log.Debugf("server: successfully initialized [%s]", time.Since(start))
|
||||
|
||||
// Start HTTP server.
|
||||
go func() {
|
||||
log.Infof("http: starting web server at %s", server.Addr)
|
||||
log.Infof("server: listening at %s", server.Addr)
|
||||
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
log.Info("http: web server shutdown complete")
|
||||
log.Info("server: shutdown complete")
|
||||
} else {
|
||||
log.Errorf("http: web server closed unexpect: %s", err)
|
||||
log.Errorf("server: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Graceful HTTP server shutdown.
|
||||
<-ctx.Done()
|
||||
log.Info("http: shutting down web server")
|
||||
log.Info("server: shutting down")
|
||||
err := server.Close()
|
||||
if err != nil {
|
||||
log.Errorf("http: web server shutdown failed: %v", err)
|
||||
log.Errorf("server: shutdown failed (%s)", err)
|
||||
}
|
||||
}
|
||||
|
|
37
internal/server/server_ext.go
Normal file
37
internal/server/server_ext.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
extMutex sync.Mutex
|
||||
extensions atomic.Value
|
||||
)
|
||||
|
||||
// Extension represents a named package extension with callbacks.
|
||||
type Extension struct {
|
||||
name string
|
||||
init func(router *gin.Engine, conf *config.Config) error
|
||||
}
|
||||
|
||||
// Register registers a new package extension.
|
||||
func Register(name string, init func(router *gin.Engine, conf *config.Config) error) {
|
||||
extMutex.Lock()
|
||||
h, _ := extensions.Load().([]Extension)
|
||||
extensions.Store(append(h, Extension{name, init}))
|
||||
extMutex.Unlock()
|
||||
}
|
||||
|
||||
// Extensions returns all registered package extensions.
|
||||
func Extensions() (ext []Extension) {
|
||||
extMutex.Lock()
|
||||
ext, _ = extensions.Load().([]Extension)
|
||||
extMutex.Unlock()
|
||||
return ext
|
||||
}
|
Loading…
Reference in a new issue