1
Fork 0

Add admin setting to change max concurrent workers

This commit is contained in:
viktorstrate 2020-09-21 21:32:30 +02:00
parent a4f53cbc35
commit 9c9807ecc9
14 changed files with 568 additions and 257 deletions

View File

@ -0,0 +1,4 @@
ALTER TABLE site_info
DROP COLUMN IF EXISTS periodic_scan_interval,
DrOP COLUMN IF EXISTS concurrent_workers;

View File

@ -1,3 +1,4 @@
ALTER TABLE site_info
ADD COLUMN IF NOT EXISTS periodic_scan_interval int(8) NOT NULL DEFAULT 0;
ADD COLUMN IF NOT EXISTS periodic_scan_interval int(8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS concurrent_workers int(8) NOT NULL DEFAULT 3;

View File

@ -114,20 +114,21 @@ type ComplexityRoot struct {
}
Mutation struct {
AuthorizeUser func(childComplexity int, username string, password string) int
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int
SetPeriodicScanInterval func(childComplexity int, interval int) int
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
UpdateUser func(childComplexity int, id int, username *string, rootPath *string, password *string, admin *bool) int
AuthorizeUser func(childComplexity int, username string, password string) int
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int
SetPeriodicScanInterval func(childComplexity int, interval int) int
SetScannerConcurrentWorkers func(childComplexity int, workers int) int
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
UpdateUser func(childComplexity int, id int, username *string, rootPath *string, password *string, admin *bool) int
}
Notification struct {
@ -178,6 +179,7 @@ type ComplexityRoot struct {
}
SiteInfo struct {
ConcurrentWorkers func(childComplexity int) int
InitialSetup func(childComplexity int) int
PeriodicScanInterval func(childComplexity int) int
}
@ -243,6 +245,7 @@ type MutationResolver interface {
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
DeleteUser(ctx context.Context, id int) (*models.User, error)
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
}
type QueryResolver interface {
SiteInfo(ctx context.Context) (*models.SiteInfo, error)
@ -727,6 +730,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.SetPeriodicScanInterval(childComplexity, args["interval"].(int)), true
case "Mutation.setScannerConcurrentWorkers":
if e.complexity.Mutation.SetScannerConcurrentWorkers == nil {
break
}
args, err := ec.field_Mutation_setScannerConcurrentWorkers_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.SetScannerConcurrentWorkers(childComplexity, args["workers"].(int)), true
case "Mutation.shareAlbum":
if e.complexity.Mutation.ShareAlbum == nil {
break
@ -1027,6 +1042,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ShareToken.Token(childComplexity), true
case "SiteInfo.concurrentWorkers":
if e.complexity.SiteInfo.ConcurrentWorkers == nil {
break
}
return e.complexity.SiteInfo.ConcurrentWorkers(childComplexity), true
case "SiteInfo.initialSetup":
if e.complexity.SiteInfo.InitialSetup == nil {
break
@ -1327,6 +1349,9 @@ type Mutation {
a value of 0 will disable periodic scans
"""
setPeriodicScanInterval(interval: Int!): Int!
"Set max number of concurrent scanner jobs running at once"
setScannerConcurrentWorkers(workers: Int!): Int!
}
type Subscription {
@ -1387,6 +1412,8 @@ type SiteInfo {
initialSetup: Boolean!
"How often automatic scans should be initiated in seconds"
periodicScanInterval: Int! @isAdmin
"How many max concurrent scanner jobs that should run at once"
concurrentWorkers: Int! @isAdmin
}
type User {
@ -1783,6 +1810,21 @@ func (ec *executionContext) field_Mutation_setPeriodicScanInterval_args(ctx cont
return args, nil
}
func (ec *executionContext) field_Mutation_setScannerConcurrentWorkers_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 int
if tmp, ok := rawArgs["workers"]; ok {
ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("workers"))
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["workers"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_shareAlbum_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -4203,6 +4245,47 @@ func (ec *executionContext) _Mutation_setPeriodicScanInterval(ctx context.Contex
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_setScannerConcurrentWorkers(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_setScannerConcurrentWorkers_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().SetScannerConcurrentWorkers(rctx, args["workers"].(int))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Notification_key(ctx context.Context, field graphql.CollectedField, obj *models.Notification) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -5503,6 +5586,60 @@ func (ec *executionContext) _SiteInfo_periodicScanInterval(ctx context.Context,
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _SiteInfo_concurrentWorkers(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "SiteInfo",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ConcurrentWorkers, nil
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
return nil, errors.New("directive isAdmin is not implemented")
}
return ec.directives.IsAdmin(ctx, obj, directive0)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, err
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(int); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Subscription_notification(ctx context.Context, field graphql.CollectedField) (ret func() graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -7626,6 +7763,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "setScannerConcurrentWorkers":
out.Values[i] = ec._Mutation_setScannerConcurrentWorkers(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@ -8041,6 +8183,11 @@ func (ec *executionContext) _SiteInfo(ctx context.Context, sel ast.SelectionSet,
if out.Values[i] == graphql.Null {
invalids++
}
case "concurrentWorkers":
out.Values[i] = ec._SiteInfo_concurrentWorkers(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}

View File

@ -56,6 +56,8 @@ type SiteInfo struct {
InitialSetup bool `json:"initialSetup"`
// How often automatic scans should be initiated in seconds
PeriodicScanInterval int `json:"periodicScanInterval"`
// How many max concurrent scanner jobs that should run at once
ConcurrentWorkers int `json:"concurrentWorkers"`
}
type MediaType string

View File

@ -20,7 +20,8 @@ func GetSiteInfo(db *sql.DB) (*SiteInfo, error) {
}
var initialSetup bool
var periodicScanInterval int = 0
var periodicScanInterval int
var concurrentWorkers int
if !rows.Next() {
// Entry does not exist
@ -29,7 +30,7 @@ func GetSiteInfo(db *sql.DB) (*SiteInfo, error) {
}
initialSetup = true
} else {
if err := rows.Scan(&initialSetup, &periodicScanInterval); err != nil {
if err := rows.Scan(&initialSetup, &periodicScanInterval, &concurrentWorkers); err != nil {
return nil, err
}
}
@ -37,5 +38,6 @@ func GetSiteInfo(db *sql.DB) (*SiteInfo, error) {
return &SiteInfo{
InitialSetup: initialSetup,
PeriodicScanInterval: periodicScanInterval,
ConcurrentWorkers: concurrentWorkers,
}, nil
}

View File

@ -62,3 +62,25 @@ func (r *mutationResolver) SetPeriodicScanInterval(ctx context.Context, interval
return dbInterval, nil
}
func (r *mutationResolver) SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error) {
if workers < 0 {
return 0, errors.New("concurrent workers must be positive")
}
_, err := r.Database.Exec("UPDATE site_info SET concurrent_workers = ?", workers)
if err != nil {
return 0, err
}
var dbWorkers int
row := r.Database.QueryRow("SELECT concurrent_workers FROM site_info")
if err = row.Scan(&dbWorkers); err != nil {
return 0, err
}
scanner.ChangeScannerConcurrentWorkers(dbWorkers)
return dbWorkers, nil
}

View File

@ -98,6 +98,9 @@ type Mutation {
a value of 0 will disable periodic scans
"""
setPeriodicScanInterval(interval: Int!): Int!
"Set max number of concurrent scanner jobs running at once"
setScannerConcurrentWorkers(workers: Int!): Int!
}
type Subscription {
@ -158,6 +161,8 @@ type SiteInfo {
initialSetup: Boolean!
"How often automatic scans should be initiated in seconds"
periodicScanInterval: Int! @isAdmin
"How many max concurrent scanner jobs that should run at once"
concurrentWorkers: Int! @isAdmin
}
type User {

View File

@ -37,16 +37,33 @@ type ScannerQueue struct {
var global_scanner_queue ScannerQueue
func InitializeScannerQueue(db *sql.DB) {
func InitializeScannerQueue(db *sql.DB) error {
var concurrentWorkers int
row := db.QueryRow("SELECT concurrent_workers FROM site_info")
if err := row.Scan(&concurrentWorkers); err != nil {
return errors.Wrap(err, "get current workers from database")
}
global_scanner_queue = ScannerQueue{
idle_chan: make(chan bool, 1),
in_progress: make([]ScannerJob, 0),
up_next: make([]ScannerJob, 0),
db: db,
settings: ScannerQueueSettings{max_concurrent_tasks: 3},
settings: ScannerQueueSettings{max_concurrent_tasks: concurrentWorkers},
}
go global_scanner_queue.startBackgroundWorker()
return nil
}
func ChangeScannerConcurrentWorkers(newMaxWorkers int) {
global_scanner_queue.mutex.Lock()
defer global_scanner_queue.mutex.Unlock()
log.Printf("Scanner max concurrent workers changed to: %d", newMaxWorkers)
global_scanner_queue.settings.max_concurrent_tasks = newMaxWorkers
}
func (queue *ScannerQueue) startBackgroundWorker() {

View File

@ -41,8 +41,13 @@ func main() {
log.Panicf("Could not migrate database: %s\n", err)
}
scanner.InitializeScannerQueue(db)
scanner.InitializePeriodicScanner(db)
if err := scanner.InitializeScannerQueue(db); err != nil {
log.Panicf("Could not initialize scanner queue: %s\n", err)
}
if err := scanner.InitializePeriodicScanner(db); err != nil {
log.Panicf("Could not initialize periodic scanner: %s", err)
}
rootRouter := mux.NewRouter()

View File

@ -0,0 +1,237 @@
import React, { useRef, useState } from 'react'
import {
Button,
Checkbox,
Dropdown,
Icon,
Input,
Loader,
} from 'semantic-ui-react'
import { useMutation, useQuery } from 'react-apollo'
import gql from 'graphql-tag'
import styled from 'styled-components'
import {
SectionTitle,
InputLabelTitle,
InputLabelDescription,
} from './SettingsPage'
const SCAN_INTERVAL_QUERY = gql`
query scanIntervalQuery {
siteInfo {
periodicScanInterval
}
}
`
const SCAN_INTERVAL_MUTATION = gql`
mutation changeScanIntervalMutation($interval: Int!) {
setPeriodicScanInterval(interval: $interval)
}
`
const timeUnits = [
{
value: 'second',
multiplier: 1,
},
{
value: 'minute',
multiplier: 60,
},
{
value: 'hour',
multiplier: 60 * 60,
},
{
value: 'day',
multiplier: 60 * 60 * 24,
},
{
value: 'month',
multiplier: 60 * 60 * 24 * 30,
},
]
const convertToSeconds = ({ value, unit }) => {
return parseInt(value * timeUnits.find(x => x.value == unit).multiplier)
}
const convertToAppropriateUnit = ({ value, unit }) => {
if (value == 0) {
return {
unit: 'second',
value: 0,
}
}
const seconds = convertToSeconds({ value, unit })
let resultingUnit = timeUnits.first
for (const unit of timeUnits) {
if (
seconds / unit.multiplier >= 1 &&
(seconds / unit.multiplier) % 1 == 0
) {
resultingUnit = unit
} else {
break
}
}
return {
unit: resultingUnit.value,
value: seconds / resultingUnit.multiplier,
}
}
const PeriodicScanner = () => {
const [enablePeriodicScanner, setEnablePeriodicScanner] = useState(false)
const [scanInterval, setScanInterval] = useState({
value: 4,
unit: 'minute',
})
const scanIntervalServerValue = useRef(null)
const scanIntervalQuery = useQuery(SCAN_INTERVAL_QUERY, {
onCompleted(data) {
const queryScanInterval = data.siteInfo.periodicScanInterval
if (queryScanInterval == 0) {
setScanInterval({
unit: 'second',
value: '',
})
} else {
setScanInterval(
convertToAppropriateUnit({
unit: 'second',
value: queryScanInterval,
})
)
}
setEnablePeriodicScanner(queryScanInterval > 0)
},
})
const [
setScanIntervalMutation,
{ loading: scanIntervalMutationLoading },
] = useMutation(SCAN_INTERVAL_MUTATION)
const onScanIntervalCheckboxChange = checked => {
setEnablePeriodicScanner(checked)
onScanIntervalUpdate(checked ? scanInterval : { value: 0, unit: 'second' })
}
const onScanIntervalUpdate = scanInterval => {
const seconds = convertToSeconds(scanInterval)
if (scanIntervalServerValue.current != seconds) {
setScanIntervalMutation({
variables: {
interval: convertToSeconds(scanInterval),
},
})
scanIntervalServerValue.current = seconds
}
}
const scanIntervalUnits = [
{
key: 'second',
text: 'Seconds',
value: 'second',
},
{
key: 'minute',
text: 'Minutes',
value: 'minute',
},
{
key: 'hour',
text: 'Hours',
value: 'hour',
},
{
key: 'day',
text: 'Days',
value: 'day',
},
{
key: 'month',
text: 'Months',
value: 'month',
},
]
return (
<>
<h3>Periodic scanner</h3>
<div style={{ margin: '12px 0' }}>
<Checkbox
label="Enable periodic scanner"
disabled={scanIntervalQuery.loading}
checked={enablePeriodicScanner}
onChange={(_, { checked }) => onScanIntervalCheckboxChange(checked)}
/>
</div>
{enablePeriodicScanner && (
<>
<label htmlFor="periodic_scan_field">
<InputLabelTitle>Periodic scan interval</InputLabelTitle>
<InputLabelDescription>
How often the scanner should perform automatic scans of all users
</InputLabelDescription>
</label>
<Input
label={
<Dropdown
onChange={(_, { value }) => {
const newScanInterval = {
...scanInterval,
unit: value,
}
setScanInterval(newScanInterval)
onScanIntervalUpdate(newScanInterval)
}}
value={scanInterval.unit}
options={scanIntervalUnits}
/>
}
onBlur={() => onScanIntervalUpdate(scanInterval)}
onKeyDown={({ key }) =>
key == 'Enter' && onScanIntervalUpdate(scanInterval)
}
loading={scanIntervalQuery.loading}
labelPosition="right"
style={{ maxWidth: 300 }}
id="periodic_scan_field"
value={scanInterval.value}
onChange={(_, { value }) => {
setScanInterval(x => ({
...x,
value,
}))
}}
/>
</>
)}
<Loader
active={scanIntervalQuery.loading || scanIntervalMutationLoading}
inline
size="small"
style={{ marginLeft: 16 }}
/>
</>
)
}
export default PeriodicScanner

View File

@ -0,0 +1,80 @@
import gql from 'graphql-tag'
import React, { useRef, useState } from 'react'
import { useQuery, useMutation } from 'react-apollo'
import { Input, Loader } from 'semantic-ui-react'
import { InputLabelTitle, InputLabelDescription } from './SettingsPage'
const CONCURRENT_WORKERS_QUERY = gql`
query concurrentWorkersQuery {
siteInfo {
concurrentWorkers
}
}
`
const SET_CONCURRENT_WORKERS_MUTATION = gql`
mutation setConcurrentWorkers($workers: Int!) {
setScannerConcurrentWorkers(workers: $workers)
}
`
const ScannerConcurrentWorkers = () => {
const workerAmountQuery = useQuery(CONCURRENT_WORKERS_QUERY, {
onCompleted(data) {
setWorkerAmount(data.siteInfo.concurrentWorkers)
workerAmountServerValue.current = data.siteInfo.concurrentWorkers
},
})
const [setWorkersMutation, workersMutationData] = useMutation(
SET_CONCURRENT_WORKERS_MUTATION
)
const workerAmountServerValue = useRef(null)
const [workerAmount, setWorkerAmount] = useState('')
const updateWorkerAmount = workerAmount => {
if (workerAmountServerValue.current != workerAmount) {
workerAmountServerValue.current = workerAmount
setWorkersMutation({
variables: {
workers: workerAmount,
},
})
}
}
return (
<div style={{ marginTop: 32 }}>
<label htmlFor="scanner_concurrent_workers_field">
<InputLabelTitle>Scanner Concurrent Workers</InputLabelTitle>
<InputLabelDescription>
The maximum amount of scanner jobs that is allowed to run at once
</InputLabelDescription>
</label>
<Input
disabled={workerAmountQuery.loading || workersMutationData.loading}
type="number"
min="1"
max="24"
id="scanner_concurrent_workers_field"
value={workerAmount}
onChange={(_, { value }) => {
setWorkerAmount(value)
}}
onBlur={() => updateWorkerAmount(workerAmount)}
onKeyDown={({ key }) =>
key == 'Enter' && updateWorkerAmount(workerAmount)
}
/>
<Loader
active={workerAmountQuery.loading || workersMutationData.loading}
inline
size="small"
style={{ marginLeft: 16 }}
/>
</div>
)
}
export default ScannerConcurrentWorkers

View File

@ -1,17 +1,10 @@
import React, { useRef, useState } from 'react'
import {
Button,
Checkbox,
Dropdown,
Icon,
Input,
Loader,
} from 'semantic-ui-react'
import { useMutation, useQuery } from 'react-apollo'
import gql from 'graphql-tag'
import styled from 'styled-components'
import { SectionTitle } from './SettingsPage'
import React from 'react'
import { useMutation } from 'react-apollo'
import { Button, Icon } from 'semantic-ui-react'
import PeriodicScanner from './PeriodicScanner'
import ScannerConcurrentWorkers from './ScannerConcurrentWorkers'
import { SectionTitle, InputLabelDescription } from './SettingsPage'
const SCAN_MUTATION = gql`
mutation scanAllMutation {
@ -22,174 +15,15 @@ const SCAN_MUTATION = gql`
}
`
const SCAN_INTERVAL_QUERY = gql`
query scanIntervalQuery {
siteInfo {
periodicScanInterval
}
}
`
const SCAN_INTERVAL_MUTATION = gql`
mutation changeScanIntervalMutation($interval: Int!) {
setPeriodicScanInterval(interval: $interval)
}
`
const timeUnits = [
{
value: 'second',
multiplier: 1,
},
{
value: 'minute',
multiplier: 60,
},
{
value: 'hour',
multiplier: 60 * 60,
},
{
value: 'day',
multiplier: 60 * 60 * 24,
},
{
value: 'month',
multiplier: 60 * 60 * 24 * 30,
},
]
const convertToSeconds = ({ value, unit }) => {
return parseInt(value * timeUnits.find(x => x.value == unit).multiplier)
}
const convertToAppropriateUnit = ({ value, unit }) => {
if (value == 0) {
return {
unit: 'second',
value: 0,
}
}
const seconds = convertToSeconds({ value, unit })
let resultingUnit = timeUnits.first
for (const unit of timeUnits) {
if (
seconds / unit.multiplier >= 1 &&
(seconds / unit.multiplier) % 1 == 0
) {
resultingUnit = unit
} else {
break
}
}
return {
unit: resultingUnit.value,
value: seconds / resultingUnit.multiplier,
}
}
const InputLabelTitle = styled.p`
font-size: 1.1em;
font-weight: 600;
margin: 1em 0 0 !important;
`
const InputLabelDescription = styled.p`
font-size: 0.9em;
margin: 0 0 0.5em !important;
`
const ScannerSection = () => {
const [startScanner, { called }] = useMutation(SCAN_MUTATION)
const [enablePeriodicScanner, setEnablePeriodicScanner] = useState(false)
const [scanInterval, setScanInterval] = useState({
value: 4,
unit: 'minute',
})
const scanIntervalServerValue = useRef(null)
const scanIntervalQuery = useQuery(SCAN_INTERVAL_QUERY, {
onCompleted(data) {
const queryScanInterval = data.siteInfo.periodicScanInterval
if (queryScanInterval == 0) {
setScanInterval({
unit: 'second',
value: '',
})
} else {
setScanInterval(
convertToAppropriateUnit({
unit: 'second',
value: queryScanInterval,
})
)
}
setEnablePeriodicScanner(queryScanInterval > 0)
},
})
const [
setScanIntervalMutation,
{ loading: scanIntervalMutationLoading },
] = useMutation(SCAN_INTERVAL_MUTATION)
const onScanIntervalCheckboxChange = checked => {
setEnablePeriodicScanner(checked)
onScanIntervalUpdate(checked ? scanInterval : { value: 0, unit: 'second' })
}
const onScanIntervalUpdate = scanInterval => {
const seconds = convertToSeconds(scanInterval)
if (scanIntervalServerValue.current != seconds) {
setScanIntervalMutation({
variables: {
interval: convertToSeconds(scanInterval),
},
})
scanIntervalServerValue.current = seconds
}
}
const scanIntervalUnits = [
{
key: 'second',
text: 'Seconds',
value: 'second',
},
{
key: 'minute',
text: 'Minutes',
value: 'minute',
},
{
key: 'hour',
text: 'Hours',
value: 'hour',
},
{
key: 'day',
text: 'Days',
value: 'day',
},
{
key: 'month',
text: 'Months',
value: 'month',
},
]
return (
<div>
<SectionTitle nospace>Scanner</SectionTitle>
<InputLabelDescription>
Will scan all users for new or updated media
</InputLabelDescription>
<Button
icon
labelPosition="left"
@ -199,68 +33,10 @@ const ScannerSection = () => {
disabled={called}
>
<Icon name="sync" />
Scan All
Scan all users
</Button>
<h3>Periodic scanner</h3>
<div style={{ margin: '12px 0' }}>
<Checkbox
label="Enable periodic scanner"
disabled={scanIntervalQuery.loading}
checked={enablePeriodicScanner}
onChange={(_, { checked }) => onScanIntervalCheckboxChange(checked)}
/>
</div>
{enablePeriodicScanner && (
<>
<label htmlFor="periodic_scan_field">
<InputLabelTitle>Periodic scan interval</InputLabelTitle>
<InputLabelDescription>
How often the scanner should perform automatic scans of all users
</InputLabelDescription>
</label>
<Input
label={
<Dropdown
onChange={(_, { value }) => {
const newScanInterval = {
...scanInterval,
unit: value,
}
setScanInterval(newScanInterval)
onScanIntervalUpdate(newScanInterval)
}}
value={scanInterval.unit}
options={scanIntervalUnits}
/>
}
onBlur={() => onScanIntervalUpdate(scanInterval)}
onKeyDown={({ key }) =>
key == 'Enter' && onScanIntervalUpdate(scanInterval)
}
loading={scanIntervalQuery.loading}
labelPosition="right"
style={{ maxWidth: 300 }}
id="periodic_scan_field"
value={scanInterval.value}
onChange={(_, { value }) => {
setScanInterval(x => ({
...x,
value,
}))
}}
/>
</>
)}
<Loader
active={scanIntervalQuery.loading || scanIntervalMutationLoading}
inline
size="small"
style={{ marginLeft: 16 }}
/>
<PeriodicScanner />
<ScannerConcurrentWorkers />
</div>
)
}

View File

@ -12,6 +12,17 @@ export const SectionTitle = styled.h2`
border-bottom: 1px solid #ddd;
`
export const InputLabelTitle = styled.p`
font-size: 1.1em;
font-weight: 600;
margin: 1em 0 0 !important;
`
export const InputLabelDescription = styled.p`
font-size: 0.9em;
margin: 0 0 0.5em !important;
`
const SettingsPage = () => {
return (
<Layout>

View File

@ -145,6 +145,7 @@ const UserRow = ({ user, refetchUsers }) => {
<Table.Row>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.username}
value={state.username}
onChange={e => updateInput(e, 'username')}
@ -152,6 +153,7 @@ const UserRow = ({ user, refetchUsers }) => {
</Table.Cell>
<Table.Cell>
<Input
style={{ width: '100%' }}
placeholder={user.rootPath}
value={state.rootPath}
onChange={e => updateInput(e, 'rootPath')}