From 9c9807ecc998e8943581ae20ee658f08a202e5bc Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Mon, 21 Sep 2020 21:32:30 +0200 Subject: [PATCH] Add admin setting to change max concurrent workers --- .../002_site_info_more_settings.down.sql | 4 + .../002_site_info_more_settings.up.sql | 3 +- api/graphql/generated.go | 175 +++++++++++- api/graphql/models/generated.go | 2 + api/graphql/models/site_info.go | 6 +- api/graphql/resolvers/scanner.go | 22 ++ api/graphql/schema.graphql | 5 + api/scanner/queue.go | 21 +- api/server.go | 9 +- ui/src/Pages/SettingsPage/PeriodicScanner.js | 237 +++++++++++++++++ .../SettingsPage/ScannerConcurrentWorkers.js | 80 ++++++ ui/src/Pages/SettingsPage/ScannerSection.js | 248 +----------------- ui/src/Pages/SettingsPage/SettingsPage.js | 11 + ui/src/Pages/SettingsPage/UserRow.js | 2 + 14 files changed, 568 insertions(+), 257 deletions(-) create mode 100644 api/database/migrations/002_site_info_more_settings.down.sql create mode 100644 ui/src/Pages/SettingsPage/PeriodicScanner.js create mode 100644 ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.js diff --git a/api/database/migrations/002_site_info_more_settings.down.sql b/api/database/migrations/002_site_info_more_settings.down.sql new file mode 100644 index 00000000..06053213 --- /dev/null +++ b/api/database/migrations/002_site_info_more_settings.down.sql @@ -0,0 +1,4 @@ + +ALTER TABLE site_info + DROP COLUMN IF EXISTS periodic_scan_interval, + DrOP COLUMN IF EXISTS concurrent_workers; diff --git a/api/database/migrations/002_site_info_more_settings.up.sql b/api/database/migrations/002_site_info_more_settings.up.sql index 47db8161..272bbfea 100644 --- a/api/database/migrations/002_site_info_more_settings.up.sql +++ b/api/database/migrations/002_site_info_more_settings.up.sql @@ -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; diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 902ea537..5f3e7a77 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -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)) } diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index ca31bbbc..3413a2e6 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -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 diff --git a/api/graphql/models/site_info.go b/api/graphql/models/site_info.go index ea362bdb..4bec8fda 100644 --- a/api/graphql/models/site_info.go +++ b/api/graphql/models/site_info.go @@ -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 } diff --git a/api/graphql/resolvers/scanner.go b/api/graphql/resolvers/scanner.go index 4d8bc3d2..1f16f6be 100644 --- a/api/graphql/resolvers/scanner.go +++ b/api/graphql/resolvers/scanner.go @@ -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 +} diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index d3932e66..cd97f616 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -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 { diff --git a/api/scanner/queue.go b/api/scanner/queue.go index 8ba4a2b7..03bb10ae 100644 --- a/api/scanner/queue.go +++ b/api/scanner/queue.go @@ -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() { diff --git a/api/server.go b/api/server.go index 7155c875..f9b1ff7e 100644 --- a/api/server.go +++ b/api/server.go @@ -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() diff --git a/ui/src/Pages/SettingsPage/PeriodicScanner.js b/ui/src/Pages/SettingsPage/PeriodicScanner.js new file mode 100644 index 00000000..14e8bf4f --- /dev/null +++ b/ui/src/Pages/SettingsPage/PeriodicScanner.js @@ -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 ( + <> +

Periodic scanner

+ +
+ onScanIntervalCheckboxChange(checked)} + /> +
+ + {enablePeriodicScanner && ( + <> + + { + 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, + })) + }} + /> + + )} + + + ) +} + +export default PeriodicScanner diff --git a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.js b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.js new file mode 100644 index 00000000..82a4c063 --- /dev/null +++ b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.js @@ -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 ( +
+ + { + setWorkerAmount(value) + }} + onBlur={() => updateWorkerAmount(workerAmount)} + onKeyDown={({ key }) => + key == 'Enter' && updateWorkerAmount(workerAmount) + } + /> + +
+ ) +} + +export default ScannerConcurrentWorkers diff --git a/ui/src/Pages/SettingsPage/ScannerSection.js b/ui/src/Pages/SettingsPage/ScannerSection.js index 5f1be8c6..7f720b38 100644 --- a/ui/src/Pages/SettingsPage/ScannerSection.js +++ b/ui/src/Pages/SettingsPage/ScannerSection.js @@ -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 (
Scanner + + Will scan all users for new or updated media + - -

Periodic scanner

- -
- onScanIntervalCheckboxChange(checked)} - /> -
- - {enablePeriodicScanner && ( - <> - - { - 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, - })) - }} - /> - - )} - + +
) } diff --git a/ui/src/Pages/SettingsPage/SettingsPage.js b/ui/src/Pages/SettingsPage/SettingsPage.js index 11b9394c..abd73e05 100644 --- a/ui/src/Pages/SettingsPage/SettingsPage.js +++ b/ui/src/Pages/SettingsPage/SettingsPage.js @@ -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 ( diff --git a/ui/src/Pages/SettingsPage/UserRow.js b/ui/src/Pages/SettingsPage/UserRow.js index c2a3a43f..d5802002 100644 --- a/ui/src/Pages/SettingsPage/UserRow.js +++ b/ui/src/Pages/SettingsPage/UserRow.js @@ -145,6 +145,7 @@ const UserRow = ({ user, refetchUsers }) => { updateInput(e, 'username')} @@ -152,6 +153,7 @@ const UserRow = ({ user, refetchUsers }) => { updateInput(e, 'rootPath')}