Add admin setting to change max concurrent workers
This commit is contained in:
parent
a4f53cbc35
commit
9c9807ecc9
|
@ -0,0 +1,4 @@
|
|||
|
||||
ALTER TABLE site_info
|
||||
DROP COLUMN IF EXISTS periodic_scan_interval,
|
||||
DrOP COLUMN IF EXISTS concurrent_workers;
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')}
|
||||
|
|
Loading…
Reference in New Issue