소스 검색

Added scheduler back

Toby Chui 2 년 전
부모
커밋
9539fdddab
7개의 변경된 파일1376개의 추가작업 그리고 871개의 파일을 삭제
  1. 1 0
      mod/agi/agi.go
  2. 233 0
      mod/time/scheduler/handlers.go
  3. 41 0
      mod/time/scheduler/helper.go
  4. 230 0
      mod/time/scheduler/scheduler.go
  5. 185 184
      mod/user/user.go
  6. 66 56
      scheduler.go
  7. 620 631
      web/SystemAO/arsm/scheduler.html

+ 1 - 0
mod/agi/agi.go

@@ -340,6 +340,7 @@ func (g *Gateway) ExecuteAGIScript(scriptContent string, fsh *filesystem.FileSys
 
 /*
 	Execute AGI script with given user information
+	scriptFile must be realpath resolved by fsa VirtualPathToRealPath function
 	Pass in http.Request pointer to enable serverless GET / POST request
 */
 func (g *Gateway) ExecuteAGIScriptAsUser(fsh *filesystem.FileSystemHandler, scriptFile string, targetUser *user.User, r *http.Request) (string, error) {

+ 233 - 0
mod/time/scheduler/handlers.go

@@ -0,0 +1,233 @@
+package scheduler
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+	"time"
+
+	"imuslab.com/arozos/mod/common"
+)
+
+//List all the jobs related to the given user
+func (a *Scheduler) HandleListJobs(w http.ResponseWriter, r *http.Request) {
+	userinfo, err := a.options.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	//Get username from user info
+	username := userinfo.Username
+
+	//Check if the user request list all
+	listAll := false
+	la, _ := common.Mv(r, "listall", false)
+	if la == "true" && userinfo.IsAdmin() {
+		listAll = true
+	}
+
+	//Find the scheduled task that belongs to this user
+	userCreatedJobs := []*Job{}
+
+	for _, thisJob := range a.jobs {
+		if listAll {
+			//List all the user jobs.
+			userCreatedJobs = append(userCreatedJobs, thisJob)
+		} else {
+			//Only list user's job
+			if thisJob.Creator == username {
+				userCreatedJobs = append(userCreatedJobs, thisJob)
+			}
+		}
+
+	}
+
+	//Return the values as json
+	js, _ := json.Marshal(userCreatedJobs)
+	common.SendJSONResponse(w, string(js))
+}
+
+func (a *Scheduler) HandleAddJob(w http.ResponseWriter, r *http.Request) {
+	userinfo, err := a.options.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	//Get required paramaters
+	taskName, err := common.Mv(r, "name", true)
+	if err != nil {
+		common.SendErrorResponse(w, "Invalid task name")
+		return
+	}
+
+	//Check taskname length valid
+	if len(taskName) > 32 {
+		common.SendErrorResponse(w, "Task name must be shorter than 32 characters")
+		return
+	}
+
+	//Check if the name already existsed
+	for _, runningJob := range a.jobs {
+		if runningJob.Name == taskName {
+			common.SendErrorResponse(w, "Task Name already occupied")
+			return
+		}
+	}
+
+	scriptpath, err := common.Mv(r, "path", true)
+	if err != nil {
+		common.SendErrorResponse(w, "Invalid script path")
+		return
+	}
+
+	//Can be empty
+	jobDescription, _ := common.Mv(r, "desc", true)
+	fsh, err := userinfo.GetFileSystemHandlerFromVirtualPath(scriptpath)
+	if err != nil {
+		common.SendErrorResponse(w, err.Error())
+		return
+	}
+	fshAbs := fsh.FileSystemAbstraction
+	realScriptPath, err := fshAbs.VirtualPathToRealPath(scriptpath, userinfo.Username)
+	if err != nil {
+		common.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Check if the file exists
+	if !fshAbs.FileExists(realScriptPath) {
+		common.SendErrorResponse(w, "script file not exists")
+		return
+	}
+
+	interval := int64(86400) //default 1 day in seconds
+	intervalString, err := common.Mv(r, "interval", true)
+	if err != nil {
+		//Default 1 day
+
+	} else {
+		//Parse the intervalString into int
+		intervalInt, err := strconv.ParseInt(intervalString, 10, 64)
+		if err != nil {
+			//Failed to parse interval to int
+			common.SendErrorResponse(w, "invalid interval")
+			return
+		}
+
+		interval = intervalInt
+	}
+
+	baseUnixTime := time.Now().Unix()
+	baseTimeString, err := common.Mv(r, "base", true)
+	if err != nil {
+		//Use curent timestamp as base
+
+	} else {
+		baseTimeInt, err := strconv.Atoi(baseTimeString)
+		if err != nil {
+			//Failed to parse interval to int
+			common.SendErrorResponse(w, "Invalid Base Time")
+			return
+		}
+
+		baseUnixTime = int64(baseTimeInt)
+	}
+
+	//Create a new job
+	newJob := Job{
+		Name:              taskName,
+		Creator:           userinfo.Username,
+		Description:       jobDescription,
+		ExecutionInterval: int64(interval),
+		BaseTime:          baseUnixTime,
+		ScriptVpath:       scriptpath,
+		FshID:             fsh.UUID,
+	}
+
+	//Write current job lists to file
+	a.jobs = append(a.jobs, &newJob)
+	a.saveJobsToCronFile()
+
+	//OK
+	common.SendOK(w)
+}
+
+func (a *Scheduler) HandleJobRemoval(w http.ResponseWriter, r *http.Request) {
+	userinfo, err := a.options.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	//Get required paramaters
+	taskName, err := common.Mv(r, "name", true)
+	if err != nil {
+		common.SendErrorResponse(w, "Invalid task name")
+		return
+	}
+
+	//Check if Job exists
+	if !a.JobExists(taskName) {
+		//Job with that name not exists
+		common.SendErrorResponse(w, "Job not exists")
+		return
+	}
+
+	targetJob := a.GetScheduledJobByName(taskName)
+
+	//Job exists. Check if the job is created by the user.
+	//User can only remove job created by himself or all job is he is admin
+	allowRemove := false
+	if !userinfo.IsAdmin() && targetJob.Creator == userinfo.Username {
+		allowRemove = true
+	} else if userinfo.IsAdmin() {
+		allowRemove = true
+	}
+
+	if !allowRemove {
+		common.SendErrorResponse(w, "Permission Denied")
+		return
+	}
+
+	//Ok. Remove Job from the list
+	a.RemoveJobFromScheduleList(taskName)
+
+	//Write current job lists to file
+	a.saveJobsToCronFile()
+
+	common.SendOK(w)
+}
+
+//Deprecated. Replace with system wide logger
+/*
+func (a *Scheduler) HandleShowLog(w http.ResponseWriter, r *http.Request) {
+	filename, _ := mv(r, "filename", false)
+	if filename == "" {
+		//Show index
+		logFiles, _ := filepath.Glob(logFolder + "*.log")
+
+		//Convert all to linux syntax
+		linuxLogFiles := []string{}
+		for _, lf := range logFiles {
+			linuxLogFiles = append(linuxLogFiles, filepath.Base(lf))
+		}
+		js, _ := json.Marshal(linuxLogFiles)
+		sendJSONResponse(w, string(js))
+	} else {
+		//Show log content
+		filename = strings.ReplaceAll(filepath.ToSlash(filename), "/", "")
+		if fileExists(filepath.Join(logFolder, filename)) {
+			logContent, err := ioutil.ReadFile(filepath.Join(logFolder, filename))
+			if err != nil {
+				sendTextResponse(w, "Unable to load log file: "+filename)
+			} else {
+				sendTextResponse(w, string(logContent))
+			}
+		} else {
+			sendTextResponse(w, "Unable to load log file: "+filename)
+		}
+	}
+}
+*/

+ 41 - 0
mod/time/scheduler/helper.go

@@ -0,0 +1,41 @@
+package scheduler
+
+import (
+	"encoding/json"
+	"io/ioutil"
+)
+
+//Load a list of jobs from file
+func loadJobsFromFile(cronfile string) ([]*Job, error) {
+	//Try to read the cronfile
+	filecontent, err := ioutil.ReadFile(cronfile)
+	if err != nil {
+		return []*Job{}, err
+	}
+
+	//Phrase the cronfile
+	prevousJobs := []*Job{}
+	err = json.Unmarshal(filecontent, &prevousJobs)
+	if err != nil {
+		return []*Job{}, err
+	}
+
+	return prevousJobs, nil
+}
+
+//save the changes in job list to file
+func (a *Scheduler) saveJobsToCronFile() error {
+	js, err := json.Marshal(a.jobs)
+	if err != nil {
+		return err
+	}
+	return ioutil.WriteFile(a.options.CronFile, js, 0775)
+}
+
+func (a *Scheduler) cronlog(message string) {
+	a.options.Logger.PrintAndLog("Scheduler", message, nil)
+}
+
+func (a *Scheduler) cronlogError(message string, err error) {
+	a.options.Logger.PrintAndLog("Scheduler", message, err)
+}

+ 230 - 0
mod/time/scheduler/scheduler.go

@@ -0,0 +1,230 @@
+package scheduler
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+	"time"
+
+	"imuslab.com/arozos/mod/agi"
+	"imuslab.com/arozos/mod/common"
+	"imuslab.com/arozos/mod/info/logger"
+	"imuslab.com/arozos/mod/user"
+)
+
+/*
+	ArozOS System Scheduler
+	author: tobychui
+
+	This module provide scheduling executable feature for ArozOS
+	Some feature was migrated from the v1.113 aecron module
+*/
+
+type Job struct {
+	Name              string //The name of this job
+	Creator           string //The creator of this job. When execute, this user permission will be used
+	Description       string //Job description, can be empty
+	ExecutionInterval int64  //Execuation interval in seconds
+	BaseTime          int64  //Exeuction basetime. The next interval is calculated using (current time - base time ) % execution interval
+	FshID             string //The target FSH ID that this script file is stored
+	ScriptVpath       string //The agi script file being called, require Vpath
+
+	lastExecutionTime   int64  //Last time this job being executed
+	lastExecutionOutput string //The output of last execution
+}
+
+type ScheudlerOption struct {
+	UserHandler *user.UserHandler
+	Gateway     *agi.Gateway
+	Logger      *logger.Logger
+	CronFile    string //The location of the cronfile which store the jobs registry in file format
+}
+
+type Scheduler struct {
+	jobs    []*Job
+	options *ScheudlerOption
+	ticker  chan bool
+}
+
+var ()
+
+func NewScheduler(option *ScheudlerOption) (*Scheduler, error) {
+	if !common.FileExists(option.CronFile) {
+		//Cronfile not exists. Create it
+		emptyJobList := []*Job{}
+		ls, _ := json.Marshal(emptyJobList)
+		err := ioutil.WriteFile(option.CronFile, ls, 0755)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	//Load previous jobs from file
+	jobs, err := loadJobsFromFile(option.CronFile)
+	if err != nil {
+		return nil, err
+	}
+
+	//Create the ArOZ Emulated Crontask
+	thisScheduler := Scheduler{
+		jobs:    jobs,
+		options: option,
+	}
+
+	option.Logger.PrintAndLog("Scheduler", "Scheduler started", nil)
+
+	//Start the cronjob at 1 minute ticker interval
+	go func() {
+		//Delay start: Wait until seconds = 0
+		for time.Now().Unix()%60 > 0 {
+			time.Sleep(500 * time.Millisecond)
+		}
+		stopChannel := thisScheduler.createTicker(1 * time.Minute)
+		thisScheduler.ticker = stopChannel
+		option.Logger.PrintAndLog("Scheduler", "ArozOS System Scheduler Started", nil)
+	}()
+
+	//Return the crontask
+	return &thisScheduler, nil
+}
+
+func (a *Scheduler) createTicker(duration time.Duration) chan bool {
+	ticker := time.NewTicker(duration)
+	stop := make(chan bool, 1)
+
+	go func() {
+		defer log.Println("Scheduler Stopped")
+		for {
+			select {
+			case <-ticker.C:
+				//Run jobs
+				for _, thisJob := range a.jobs {
+					if (time.Now().Unix()-thisJob.BaseTime)%thisJob.ExecutionInterval == 0 {
+						//Execute this job
+						//Get the creator userinfo
+						targetUser, err := a.options.UserHandler.GetUserInfoFromUsername(thisJob.Creator)
+						if err != nil {
+							a.cronlogError("User "+thisJob.Creator+" no longer exists", err)
+							return
+						}
+
+						//Check if the script exists
+						fsh, err := targetUser.GetFileSystemHandlerFromVirtualPath(thisJob.ScriptVpath)
+						if err != nil {
+							a.cronlogError("Unable to resolve required vpath for job: "+thisJob.Name+" for user "+thisJob.Creator, err)
+							return
+						}
+
+						rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(thisJob.ScriptVpath, targetUser.Username)
+						if err != nil {
+							a.cronlogError("Unable to resolve file real path for job: "+thisJob.Name+" for user "+thisJob.Creator, err)
+							return
+						}
+
+						if !fsh.FileSystemAbstraction.FileExists(rpath) {
+							//This job no longer exists in the file system. Remove it
+							a.cronlog("Removing job " + thisJob.Name + " by " + thisJob.Creator + " as job file no longer exists")
+							a.RemoveJobFromScheduleList(thisJob.Name)
+							return
+						}
+
+						clonedJobStructure := *thisJob
+						ext := filepath.Ext(rpath)
+						if ext == ".js" || ext == ".agi" {
+							//Run using AGI interface in go routine
+							go func(thisJob Job) {
+								//Resolve the sript path to realpath
+								//Run the script with this user scope
+								thisJob.lastExecutionTime = time.Now().Unix()
+								resp, err := a.options.Gateway.ExecuteAGIScriptAsUser(fsh, rpath, targetUser, nil)
+								if err != nil {
+									a.cronlogError(thisJob.Name+" execution error: "+err.Error(), err)
+									thisJob.lastExecutionOutput = err.Error()
+								} else {
+									a.cronlog(thisJob.Name + " executed: " + resp)
+									thisJob.lastExecutionOutput = resp
+								}
+							}(clonedJobStructure)
+
+						} else {
+							//Unknown script file. Ignore this
+							a.cronlogError("This extension is not yet supported: "+ext, errors.New("unsupported AGI interface script extension"))
+						}
+
+					}
+				}
+			case <-stop:
+				return
+			}
+		}
+	}()
+
+	return stop
+}
+
+func (a *Scheduler) Close() {
+	if a.ticker != nil {
+		//Stop the ticker
+		a.ticker <- true
+	}
+}
+
+//Add an job object to system scheduler
+func (a *Scheduler) AddJobToScheduler(job *Job) error {
+	a.jobs = append(a.jobs, job)
+	return nil
+}
+
+func (a *Scheduler) GetScheduledJobByName(name string) *Job {
+	for _, thisJob := range a.jobs {
+		if thisJob.Name == name {
+			return thisJob
+		}
+	}
+
+	return nil
+}
+
+func (a *Scheduler) RemoveJobFromScheduleList(taskName string) {
+	newJobSlice := []*Job{}
+	for _, j := range a.jobs {
+		if j.Name != taskName {
+			thisJob := j
+			newJobSlice = append(newJobSlice, thisJob)
+		}
+	}
+	a.jobs = newJobSlice
+}
+
+func (a *Scheduler) JobExists(name string) bool {
+	targetJob := a.GetScheduledJobByName(name)
+	if targetJob == nil {
+		return false
+	} else {
+		return true
+	}
+}
+
+//Write the output to log file. Default to ./system/aecron/{date}.log
+/*
+func cronlog(message string) {
+	currentTime := time.Now()
+	timestamp := currentTime.Format("2006-01-02 15:04:05")
+	message = timestamp + " " + message
+	currentLogFile := filepath.ToSlash(filepath.Clean(logFolder)) + "/" + time.Now().Format("2006-02-01") + ".log"
+	f, err := os.OpenFile(currentLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		//Unable to write to file. Log to STDOUT instead
+		log.Println(message)
+		return
+	}
+	if _, err := f.WriteString(message + "\n"); err != nil {
+		log.Println(message)
+		return
+	}
+	defer f.Close()
+
+}
+*/

+ 185 - 184
mod/user/user.go

@@ -1,184 +1,185 @@
-package user
-
-import (
-	"errors"
-	"log"
-	"net/http"
-	"os"
-
-	"golang.org/x/sync/syncmap"
-
-	auth "imuslab.com/arozos/mod/auth"
-	db "imuslab.com/arozos/mod/database"
-	permission "imuslab.com/arozos/mod/permission"
-	quota "imuslab.com/arozos/mod/quota"
-	"imuslab.com/arozos/mod/share/shareEntry"
-	storage "imuslab.com/arozos/mod/storage"
-)
-
-var (
-	//Create a buffer to put the pointers to created user quota managers, mapped by username
-	//quotaManagerBuffer map[string]*quota.QuotaHandler = map[string]*quota.QuotaHandler{}
-	quotaManagerBuffer = syncmap.Map{}
-)
-
-type User struct {
-	Username        string
-	StorageQuota    *quota.QuotaHandler
-	PermissionGroup []*permission.PermissionGroup
-	HomeDirectories *storage.StoragePool
-
-	parent *UserHandler
-}
-
-type UserHandler struct {
-	UniversalModules []string //Modules where all user can access
-
-	authAgent       *auth.AuthAgent
-	database        *db.Database
-	phandler        *permission.PermissionHandler
-	basePool        *storage.StoragePool
-	shareEntryTable **shareEntry.ShareEntryTable
-}
-
-//Initiate a new user handler
-func NewUserHandler(systemdb *db.Database, authAgent *auth.AuthAgent, permissionHandler *permission.PermissionHandler, baseStoragePool *storage.StoragePool, shareEntryTable **shareEntry.ShareEntryTable) (*UserHandler, error) {
-	return &UserHandler{
-		authAgent:       authAgent,
-		database:        systemdb,
-		phandler:        permissionHandler,
-		basePool:        baseStoragePool,
-		shareEntryTable: shareEntryTable,
-	}, nil
-}
-
-//Return the user handler's auth agent
-func (u *UserHandler) GetAuthAgent() *auth.AuthAgent {
-	return u.authAgent
-}
-
-func (u *UserHandler) GetPermissionHandler() *permission.PermissionHandler {
-	return u.phandler
-}
-
-func (u *UserHandler) GetStoragePool() *storage.StoragePool {
-	return u.basePool
-}
-
-func (u *UserHandler) GetDatabase() *db.Database {
-	return u.database
-}
-
-func (u *UserHandler) UpdateStoragePool(newpool *storage.StoragePool) {
-	u.basePool = newpool
-}
-
-//Get User object from username
-func (u *UserHandler) GetUserInfoFromUsername(username string) (*User, error) {
-	//Check if user exists
-	if !u.authAgent.UserExists(username) {
-		return &User{}, errors.New("User not exists")
-	}
-
-	//Get the user's permission group
-	permissionGroups, err := u.phandler.GetUsersPermissionGroup(username)
-	if err != nil {
-		return &User{}, err
-	}
-
-	//Create user directories in the Home Directories
-	if u.basePool.Storages == nil {
-		//This userhandler do not have a basepool?
-		log.Println("USER HANDLER DO NOT HAVE BASEPOOL")
-	} else {
-		for _, store := range u.basePool.Storages {
-			if store.Hierarchy == "user" {
-				os.MkdirAll(store.Path+"/users/"+username, 0755)
-			}
-		}
-	}
-
-	thisUser := User{
-		Username:        username,
-		PermissionGroup: permissionGroups,
-		HomeDirectories: u.basePool,
-
-		parent: u,
-	}
-
-	//Get the storage quota manager for thus user
-	var thisUserQuotaManager *quota.QuotaHandler
-	if val, ok := quotaManagerBuffer.Load(username); ok {
-		//user quota manager exists
-		thisUserQuotaManager = val.(*quota.QuotaHandler)
-	} else {
-		//Get the largest quota from the user's group
-		maxQuota := int64(0)
-		for _, group := range permissionGroups {
-			if group.DefaultStorageQuota == -1 {
-				//Admin
-				maxQuota = -1
-				break
-			} else if group.DefaultStorageQuota > maxQuota {
-				//Other groups. Get the largest one
-				maxQuota = group.DefaultStorageQuota
-			}
-		}
-
-		//Create a new manager for this user
-		allFsHandlers := thisUser.GetAllFileSystemHandler()
-		thisUserQuotaManager = quota.NewUserQuotaHandler(u.database, username, allFsHandlers, maxQuota)
-
-		if !thisUserQuotaManager.IsQuotaInitialized() {
-			//This user quota hasn't been initalized. Initalize it now to match its group
-			userMaxDefaultStorageQuota := permission.GetLargestStorageQuotaFromGroups(permissionGroups)
-			thisUserQuotaManager.SetUserStorageQuota(userMaxDefaultStorageQuota)
-		}
-		//Push the manger to buffer
-		quotaManagerBuffer.Store(username, thisUserQuotaManager)
-	}
-
-	thisUser.StorageQuota = thisUserQuotaManager
-
-	//Return the user object
-	return &thisUser, nil
-}
-
-//Get user obejct from session
-func (u *UserHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (*User, error) {
-	username, err := u.authAgent.GetUserName(w, r)
-	if err != nil {
-		return &User{}, err
-	}
-
-	userObject, err := u.GetUserInfoFromUsername(username)
-	if err != nil {
-		return &User{}, err
-	}
-	return userObject, nil
-}
-
-//Get all the users given the permission group name, super IO heavy operation
-func (u *UserHandler) GetUsersInPermissionGroup(permissionGroupName string) ([]*User, error) {
-	results := []*User{}
-	//Check if the given group exists
-	if u.phandler.GetPermissionGroupByName(permissionGroupName) == nil {
-		//Permission group with given name not exists
-		return results, errors.New("Permission group not exists")
-	}
-
-	AllRegisteredUsers := u.authAgent.ListUsers()
-	for _, thisUser := range AllRegisteredUsers {
-		thisUserInfo, err := u.GetUserInfoFromUsername(thisUser)
-		if err != nil {
-			continue
-		}
-
-		//Check if the user is in the given permission group
-		if thisUserInfo.UserIsInOneOfTheGroupOf([]string{permissionGroupName}) {
-			results = append(results, thisUserInfo)
-		}
-	}
-
-	return results, nil
-}
+package user
+
+import (
+	"errors"
+	"log"
+	"net/http"
+	"os"
+
+	"golang.org/x/sync/syncmap"
+
+	auth "imuslab.com/arozos/mod/auth"
+	db "imuslab.com/arozos/mod/database"
+	permission "imuslab.com/arozos/mod/permission"
+	quota "imuslab.com/arozos/mod/quota"
+	"imuslab.com/arozos/mod/share/shareEntry"
+	storage "imuslab.com/arozos/mod/storage"
+)
+
+var (
+	//Create a buffer to put the pointers to created user quota managers, mapped by username
+	//quotaManagerBuffer map[string]*quota.QuotaHandler = map[string]*quota.QuotaHandler{}
+	quotaManagerBuffer = syncmap.Map{}
+)
+
+type User struct {
+	Username        string
+	StorageQuota    *quota.QuotaHandler
+	PermissionGroup []*permission.PermissionGroup
+	HomeDirectories *storage.StoragePool
+
+	parent *UserHandler
+}
+
+type UserHandler struct {
+	UniversalModules []string //Modules where all user can access
+
+	authAgent       *auth.AuthAgent
+	database        *db.Database
+	phandler        *permission.PermissionHandler
+	basePool        *storage.StoragePool
+	shareEntryTable **shareEntry.ShareEntryTable
+}
+
+//Initiate a new user handler
+func NewUserHandler(systemdb *db.Database, authAgent *auth.AuthAgent, permissionHandler *permission.PermissionHandler, baseStoragePool *storage.StoragePool, shareEntryTable **shareEntry.ShareEntryTable) (*UserHandler, error) {
+	return &UserHandler{
+		authAgent:       authAgent,
+		database:        systemdb,
+		phandler:        permissionHandler,
+		basePool:        baseStoragePool,
+		shareEntryTable: shareEntryTable,
+	}, nil
+}
+
+//Return the user handler's auth agent
+func (u *UserHandler) GetAuthAgent() *auth.AuthAgent {
+	return u.authAgent
+}
+
+func (u *UserHandler) GetPermissionHandler() *permission.PermissionHandler {
+	return u.phandler
+}
+
+//Get the user's base storage pool, in most case it is the system pool
+func (u *UserHandler) GetStoragePool() *storage.StoragePool {
+	return u.basePool
+}
+
+func (u *UserHandler) GetDatabase() *db.Database {
+	return u.database
+}
+
+func (u *UserHandler) UpdateStoragePool(newpool *storage.StoragePool) {
+	u.basePool = newpool
+}
+
+//Get User object from username
+func (u *UserHandler) GetUserInfoFromUsername(username string) (*User, error) {
+	//Check if user exists
+	if !u.authAgent.UserExists(username) {
+		return &User{}, errors.New("User not exists")
+	}
+
+	//Get the user's permission group
+	permissionGroups, err := u.phandler.GetUsersPermissionGroup(username)
+	if err != nil {
+		return &User{}, err
+	}
+
+	//Create user directories in the Home Directories
+	if u.basePool.Storages == nil {
+		//This userhandler do not have a basepool?
+		log.Println("USER HANDLER DO NOT HAVE BASEPOOL")
+	} else {
+		for _, store := range u.basePool.Storages {
+			if store.Hierarchy == "user" {
+				os.MkdirAll(store.Path+"/users/"+username, 0755)
+			}
+		}
+	}
+
+	thisUser := User{
+		Username:        username,
+		PermissionGroup: permissionGroups,
+		HomeDirectories: u.basePool,
+
+		parent: u,
+	}
+
+	//Get the storage quota manager for thus user
+	var thisUserQuotaManager *quota.QuotaHandler
+	if val, ok := quotaManagerBuffer.Load(username); ok {
+		//user quota manager exists
+		thisUserQuotaManager = val.(*quota.QuotaHandler)
+	} else {
+		//Get the largest quota from the user's group
+		maxQuota := int64(0)
+		for _, group := range permissionGroups {
+			if group.DefaultStorageQuota == -1 {
+				//Admin
+				maxQuota = -1
+				break
+			} else if group.DefaultStorageQuota > maxQuota {
+				//Other groups. Get the largest one
+				maxQuota = group.DefaultStorageQuota
+			}
+		}
+
+		//Create a new manager for this user
+		allFsHandlers := thisUser.GetAllFileSystemHandler()
+		thisUserQuotaManager = quota.NewUserQuotaHandler(u.database, username, allFsHandlers, maxQuota)
+
+		if !thisUserQuotaManager.IsQuotaInitialized() {
+			//This user quota hasn't been initalized. Initalize it now to match its group
+			userMaxDefaultStorageQuota := permission.GetLargestStorageQuotaFromGroups(permissionGroups)
+			thisUserQuotaManager.SetUserStorageQuota(userMaxDefaultStorageQuota)
+		}
+		//Push the manger to buffer
+		quotaManagerBuffer.Store(username, thisUserQuotaManager)
+	}
+
+	thisUser.StorageQuota = thisUserQuotaManager
+
+	//Return the user object
+	return &thisUser, nil
+}
+
+//Get user obejct from session
+func (u *UserHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (*User, error) {
+	username, err := u.authAgent.GetUserName(w, r)
+	if err != nil {
+		return &User{}, err
+	}
+
+	userObject, err := u.GetUserInfoFromUsername(username)
+	if err != nil {
+		return &User{}, err
+	}
+	return userObject, nil
+}
+
+//Get all the users given the permission group name, super IO heavy operation
+func (u *UserHandler) GetUsersInPermissionGroup(permissionGroupName string) ([]*User, error) {
+	results := []*User{}
+	//Check if the given group exists
+	if u.phandler.GetPermissionGroupByName(permissionGroupName) == nil {
+		//Permission group with given name not exists
+		return results, errors.New("Permission group not exists")
+	}
+
+	AllRegisteredUsers := u.authAgent.ListUsers()
+	for _, thisUser := range AllRegisteredUsers {
+		thisUserInfo, err := u.GetUserInfoFromUsername(thisUser)
+		if err != nil {
+			continue
+		}
+
+		//Check if the user is in the given permission group
+		if thisUserInfo.UserIsInOneOfTheGroupOf([]string{permissionGroupName}) {
+			results = append(results, thisUserInfo)
+		}
+	}
+
+	return results, nil
+}

+ 66 - 56
scheduler.go

@@ -1,8 +1,13 @@
 package main
 
 import (
+	"net/http"
+
+	"imuslab.com/arozos/mod/common"
+	module "imuslab.com/arozos/mod/modules"
+	prout "imuslab.com/arozos/mod/prouter"
 	"imuslab.com/arozos/mod/time/nightly"
-	//"imuslab.com/arozos/mod/time/scheduler"
+	"imuslab.com/arozos/mod/time/scheduler"
 )
 
 /*
@@ -15,8 +20,8 @@ import (
 */
 
 var (
-	nightlyManager *nightly.TaskManager
-	//systemScheduler *scheduler.Scheduler
+	nightlyManager  *nightly.TaskManager
+	systemScheduler *scheduler.Scheduler
 )
 
 func NightlyTasksInit() {
@@ -37,59 +42,64 @@ func SchedulerInit() {
 		The internal scheudler for arozos
 	*/
 	//Create an user router and its module
-	/*
-		router := prout.NewModuleRouter(prout.RouterOption{
-			ModuleName:  "Tasks Scheduler",
-			AdminOnly:   false,
-			UserHandler: userHandler,
-			DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
-				common.SendErrorResponse(w, "Permission Denied")
-			},
-		})
-
-		//Register the module
-		moduleHandler.RegisterModule(module.ModuleInfo{
-			Name:        "Tasks Scheduler",
-			Group:       "System Tools",
-			IconPath:    "SystemAO/arsm/img/scheduler.png",
-			Version:     "1.2",
-			StartDir:    "SystemAO/arsm/scheduler.html",
-			SupportFW:   true,
-			InitFWSize:  []int{1080, 580},
-			LaunchFWDir: "SystemAO/arsm/scheduler.html",
-			SupportEmb:  false,
-		})
-
-		//Startup the ArOZ Emulated Crontab Service
-		newScheduler, err := scheduler.NewScheduler(userHandler, AGIGateway, "system/cron.json")
-		if err != nil {
-			systemWideLogger.PrintAndLog("Cron", "ArOZ Emulated Cron Startup Failed. Stopping all scheduled tasks.", err)
+
+	router := prout.NewModuleRouter(prout.RouterOption{
+		ModuleName:  "Tasks Scheduler",
+		AdminOnly:   false,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			common.SendErrorResponse(w, "Permission Denied")
+		},
+	})
+
+	//Register the module
+	moduleHandler.RegisterModule(module.ModuleInfo{
+		Name:        "Tasks Scheduler",
+		Group:       "System Tools",
+		IconPath:    "SystemAO/arsm/img/scheduler.png",
+		Version:     "1.2",
+		StartDir:    "SystemAO/arsm/scheduler.html",
+		SupportFW:   true,
+		InitFWSize:  []int{1080, 580},
+		LaunchFWDir: "SystemAO/arsm/scheduler.html",
+		SupportEmb:  false,
+	})
+
+	//Startup the ArOZ Emulated Crontab Service
+	newScheduler, err := scheduler.NewScheduler(&scheduler.ScheudlerOption{
+		UserHandler: userHandler,
+		Gateway:     AGIGateway,
+		Logger:      systemWideLogger,
+		CronFile:    "system/cron.json",
+	})
+	if err != nil {
+		systemWideLogger.PrintAndLog("Cron", "ArOZ Emulated Cron Startup Failed. Stopping all scheduled tasks.", err)
+	}
+
+	systemScheduler = newScheduler
+
+	//Register Endpoints
+	http.HandleFunc("/system/arsm/aecron/list", func(w http.ResponseWriter, r *http.Request) {
+		if authAgent.CheckAuth(r) {
+			//User logged in
+			newScheduler.HandleListJobs(w, r)
+		} else {
+			//User not logged in
+			errorHandlePermissionDenied(w, r)
 		}
+	})
+	router.HandleFunc("/system/arsm/aecron/add", systemScheduler.HandleAddJob)
+	router.HandleFunc("/system/arsm/aecron/remove", systemScheduler.HandleJobRemoval)
+	//router.HandleFunc("/system/arsm/aecron/listlog", systemScheduler.HandleShowLog)
+
+	//Register settings
+	registerSetting(settingModule{
+		Name:         "Tasks Scheduler",
+		Desc:         "System Tasks and Excution Scheduler",
+		IconPath:     "SystemAO/arsm/img/small_icon.png",
+		Group:        "Cluster",
+		StartDir:     "SystemAO/arsm/aecron.html",
+		RequireAdmin: false,
+	})
 
-		systemScheduler = newScheduler
-
-		//Register Endpoints
-		http.HandleFunc("/system/arsm/aecron/list", func(w http.ResponseWriter, r *http.Request) {
-			if authAgent.CheckAuth(r) {
-				//User logged in
-				newScheduler.HandleListJobs(w, r)
-			} else {
-				//User not logged in
-				errorHandlePermissionDenied(w, r)
-			}
-		})
-		router.HandleFunc("/system/arsm/aecron/add", systemScheduler.HandleAddJob)
-		router.HandleFunc("/system/arsm/aecron/remove", systemScheduler.HandleJobRemoval)
-		router.HandleFunc("/system/arsm/aecron/listlog", systemScheduler.HandleShowLog)
-
-		//Register settings
-		registerSetting(settingModule{
-			Name:         "Tasks Scheduler",
-			Desc:         "System Tasks and Excution Scheduler",
-			IconPath:     "SystemAO/arsm/img/small_icon.png",
-			Group:        "Cluster",
-			StartDir:     "SystemAO/arsm/aecron.html",
-			RequireAdmin: false,
-		})
-	*/
 }

+ 620 - 631
web/SystemAO/arsm/scheduler.html

@@ -1,632 +1,621 @@
-<!DOCTYPE html>
-<html>
-<head>
-	<meta name="mobile-web-app-capable" content="yes">
-	<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
-    <meta charset="UTF-8">
-    <title>System Task Scheduler</title>
-    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
-    <script src="../../script/jquery.min.js"></script>
-	<script src="../../script/semantic/semantic.min.js"></script>
-    <script src="../../script/ao_module.js"></script>
-    <script src="js/moment.min.js"></script>
-    <style>
-        body{
-            margin-top: 12px; 
-            padding-right: 15px;
-        }
-        .hidden{
-            display:none;
-        }
-
-        .compulsory{
-            color: red;
-            font-weight: bold;
-        }
-
-        #main{
-            overflow-y:auto;
-        }
-
-        #logPreview{
-            width: 100%;
-        }
-    </style>
-</head>
-<body>
-    <div>
-        <div class="ui stackable grid">
-            <div class="four wide column" >
-                <div class="ui vertical fluid menu">
-                    <div class="item">
-                        <img class="ui image" src="img/banner.png">
-                    </div>
-                    <a id="stb" class="active teal item functBtn" onclick="scheduleList(this);">
-                        User's Scheduled Tasks
-                    </a>
-                    <a id="asb" class="teal item functBtn" onclick="scheduleListAll(this);">
-                        All Scheduled Tasks
-                    </a>
-                    <a id="nsb" class="item functBtn" onclick="newTaskUI(this);">
-                        <i class="add icon"></i> New Scheduled Task
-                    </a>
-                    <a id="rsb" class="item functBtn" onclick="showRemoveSchedule(this);">
-                        <i class="remove icon"></i> Remove Scheduled Task
-                    </a>
-                    <div class="item">
-                        <div class="ui transparent icon input">
-                        <input type="text" id="filter" placeholder="Filter" onkeydown="handleFilterEnterKeydown(event);">
-                        <i class="search icon"></i>
-                        </div>
-                    </div>
-                    <a class="item" onclick="applyFilter();">
-                        <i class="filter icon"></i> Apply Filter
-                    </a>
-                    <a id="log" class="teal item functBtn" onclick="showLogInfo(this);">
-                        Show Logs
-                    </a>
-                </div>  
-            </div>
-            <div id="main" class="twelve wide column">
-                <div class="ui container">
-                    <br>
-                    <!-- Scheduled Task view-->
-                    <div id="scheduleView" class="view">
-                        <table class="ui celled striped table">
-                            <thead>
-                                <tr>
-                                    <th colspan="4">
-                                        Scheduled Tasks
-                                    </th>
-                                </tr>
-                                <tr>
-                                    <th>
-                                        Script
-                                    </th>
-                                    <th>
-                                        Location
-                                    </th>
-                                    <th>
-                                        Interval
-                                    </th>
-                                    <th>
-                                        Base Time
-                                    </th>
-                                </tr>
-                            </thead>
-                            <tbody id="scheduleList">
-                            
-                            </tbody>
-                        </table>
-                    </div>
-
-                     <!-- All Scheudle List-->
-                     <div id="scheudleListAll" class="view" style="display:none;">
-                        <table class="ui celled striped table">
-                            <thead>
-                                <tr>
-                                    <th colspan="5">
-                                        System Wide Scheduled Tasks
-                                    </th>
-                                </tr>
-                                <tr>
-                                    <th>
-                                        Task Name (Creator)
-                                    </th>
-                                    <th>
-                                        Script Location
-                                    </th>
-                                    <th>
-                                        Description
-                                    </th>
-                                    <th>
-                                        Interval
-                                    </th>
-                                    <th>
-                                        Base Time
-                                    </th>
-                                </tr>
-                            </thead>
-                            <tbody id="scheduleListAllContent">
-                            
-                            </tbody>
-                        </table>
-                    </div>
-
-                    <!-- New Scheduule view-->
-                    <div id="newTask" class="view hidden" >
-                        <div class="ui message">
-                            <div class="header">
-                                New Scheduled Task
-                            </div>
-                            <ul class="list">
-                                <li>You can add an agi script (.agi / .js) from one of your directories or</li>
-                                <li>A shell script (.bat or .sh) if you have admin privileges.</li>
-                            </ul>
-                            </div>
-                        <form class="ui form" onsubmit="handleTaskSubmition(event);">
-                            <div class="field">
-                            <label>Task Name (Max: 32 chars) <span class="compulsory">*</span></label>
-                            <input type="text" id="tasknanme" name="tasknanme" placeholder="New Task" maxlength="16" autocomplete="off">
-                            </div>
-                            <div class="field">
-                            <label>Description</label>
-                            <input type="text" id="desc" name="desc" placeholder="Description goes here"  autocomplete="off">
-                            </div>
-                            <div class="field">
-                                <label>Script Path <span class="compulsory">*</span></label>
-                                <div class="ui icon fluid input">
-                                    <input id="scriptpath" type="text" placeholder="Virtual path to the script file" autocomplete="off">
-                                    <i onclick="openFileSelector();" class="inverted circular search link icon"></i>
-                                </div>
-                                <div id="extensionWarning" style="display:none;" class="ui yellow small message">This extension is not supported by the scheduler. Please continue with your own risk.</div>
-                            </div>
-                            <div class="field">
-                                <label>Execution Interval <span class="compulsory">*</span></label>
-                                <div>
-                                    <div class="ui input" style="width: 200px;">
-                                        <input id="intervalvalue" type="number" placeholder="1" value="1">
-                                    </div>
-                                    <div class="ui selection dropdown">
-                                        <input type="hidden" id="intervalunit" name="intervalunit" value="60">
-                                        <i class="dropdown icon"></i>
-                                        <div class="default text">Unit</div>
-                                        <div class="menu">
-                                            <div class="item" data-value="60">Minutes</div>
-                                            <div class="item" data-value="3600">Hours</div>
-                                            <div class="item" data-value="86400">Days</div>
-                                            <div class="item" data-value="2628333">Months (approximate)</div>
-                                        </div>
-                                    </div>
-                                    <br>
-                                    <small>Execute the script above ever x unit. Month (Unit) is approximated as 2628333 seconds</small>
-                                </div>
-                            </div>
-                            <div class="field">
-                                <label>Execution Base <span class="compulsory">*</span></label>
-                                <div class="ui selection dropdown">
-                                    <input type="hidden" id="intervalbase" name="intervalbase" value="now">
-                                    <i class="dropdown icon"></i>
-                                    <div class="default text">Base Timeframe</div>
-                                    <div class="menu">
-                                        <div class="item" data-value="now">Now</div>
-                                        <div class="item" data-value="hour">Start of the Hour</div>
-                                        <div class="item" data-value="day">Start of the Day</div>
-                                        <div class="item" data-value="month">Start of the Month</div>
-                                        <div class="item" data-value="year">Start of the Year</div>
-                                        <div class="item" data-value="mon">Monday Mid-night</div>
-                                        <div class="item" data-value="tue">Tuesday Mid-night</div>
-                                        <div class="item" data-value="wed">Wednesday Mid-night</div>
-                                        <div class="item" data-value="thu">Thursday Mid-night</div>
-                                        <div class="item" data-value="fri">Friday Mid-night</div>
-                                        <div class="item" data-value="sat">Saturday Mid-night</div>
-                                        <div class="item" data-value="sun">Sunday Mid-night</div>
-                                    </div>
-                                </div>
-                                <small>The base day helps you to offset the interval for weekly / monthly based schedules.</small>
-                            </div>
-                            <button class="ui button" type="submit">Submit</button>
-                            <br> <br>
-                            <small>Fields with <span class="compulsory">*</span> are compulsory</small>
-                        </form>
-
-                        <div class="ui info message">
-                            <i class="close icon" onclick='$(this).parent().fadeOut();'></i>
-                            <div class="header">
-                                Tips for running shell scripts
-                            </div>
-                            <ul class="list">
-                                <li>The execution root of all bash script and batch script are based on the arozos root.</li>
-                                <li>Remember always use absolute path inside your scripts.</li>
-                            </ul>
-                        </div>
-                        <br><br>
-                    </div>
-
-                    <!-- Remove Scheduule view-->
-                    <div id="removeShedule" class="view hidden" >
-                        <table class="ui celled striped table">
-                            <thead>
-                                <tr>
-                                    <th class="ui red message" colspan="5">
-                                        <i class="remove icon"></i> Remove Scheduled Tasks<br>
-                                        <small>WARNING! All operations are not reversible.</small>
-                                    </th>
-                                </tr>
-                                <tr>
-                                    <th>
-                                        Task Name (Creator)
-                                    </th>
-                                    <th>
-                                        Script Location
-                                    </th>
-                                    <th>
-                                        Description
-                                    </th>
-                                    <th>
-                                        Interval
-                                    </th>
-                                    <th>
-                                        Action
-                                    </th>
-                                </tr>
-                            </thead>
-                            <tbody id="removeScheduleList">
-                            
-                            </tbody>
-                        </table>
-                    </div>
-
-                     <!-- Scheduler Log view-->
-                     <div id="listLog" class="view hidden" >
-                        <p>Select a log file to preview</p>
-                        <select id="logIndex" class="ui search fluid dropdown" onchange="loadLogFile(this.value);">
-                            <option value="">Date (DD-MM-YYYY)</option>
-                            <option value="AL">Alabama</option>
-                        </select>
-                        <div class="ui segment form">
-                            <div class="field">
-                                <textarea id="logPreview" rows="10"></textarea>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
- 
-    <script>
-        $(".ui.dropdown").dropdown();
-        initScheduleList();
-        function initScheduleList(){
-            $.ajax({
-                url: "../../system/arsm/aecron/list",
-                data: {},
-                success: function(data){
-                    $("#scheduleList").html("");
-                    if (data.length == 0){
-                        $("#scheduleList").append(`<tr>
-                            <td colspan="4">
-                                No registered schedule.
-                            </td>
-                        </tr>`);
-                    }else{
-                        data.forEach(task => {
-                            var filter = $("#filter").val().trim();
-                            if (filter != ""){
-                                //Apply filter to the results
-                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
-                                    //Matching
-                                }else{
-                                    //Not matching
-                                    return
-                                }
-                            }
-                            var scriptName = task.ScriptFile.split("/").pop();
-                            $("#scheduleList").append(`<tr>
-                                <td class="collapsing">
-                                    <i class="code file outline icon"></i> ${scriptName}
-                                </td>
-                                <td>${task.ScriptFile}</td>
-                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
-                                <td class="right aligned collapsing">${moment.unix(task.BaseTime).format('LLL')}</td>
-                            </tr>`);
-                        });
-                    }
-                }
-            });
-        }
-
-        function initScheduleFullList(){
-            $.ajax({
-                url: "../../system/arsm/aecron/list?listall=true",
-                data: {},
-                success: function(data){
-                    $("#scheduleListAllContent").html("");
-                    if (data.length == 0){
-                        $("#scheduleListAllContent").append(`<tr>
-                            <td colspan="4">
-                                No registered schedule.
-                            </td>
-                        </tr>`);
-                    }else{
-                        data.forEach(task => {
-                            var filter = $("#filter").val().trim();
-                            if (filter != ""){
-                                //Apply filter to the results
-                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
-                                    //Matching
-                                }else{
-                                    //Not matching
-                                    return
-                                }
-                            }
-
-                            var scriptLocation = task.ScriptFile;
-                            if (task.JobType == "function"){
-                                scriptLocation = "[Internal Function]"
-                            }
-                            $("#scheduleListAllContent").append(`<tr>
-                                <td class="collapsing">
-                                    <i class="user icon"></i> ${task.Name} (${task.Creator})
-                                </td>
-                                <td>${scriptLocation}</td>
-                                <td>${task.Description}</td>
-                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
-                                <td class="right aligned collapsing">${moment.unix(task.BaseTime).format('LLL')}</td>
-                            </tr>`);
-                        });
-                    }
-                }
-            });
-        }
-
-        function parseSecondsToHumanReadableFormat(seconds){
-            seconds = Number(seconds);
-            var d = Math.floor(seconds / (3600*24));
-            var h = Math.floor(seconds % (3600*24) / 3600);
-            var m = Math.floor(seconds % 3600 / 60);
-            var s = Math.floor(seconds % 60);
-
-            var dDisplay = d > 0 ? d + (d == 1 ? " day " : " days ") : "";
-            var hDisplay = h > 0 ? h + (h == 1 ? " hour " : " hours ") : "";
-            var mDisplay = m > 0 ? m + (m == 1 ? " minute " : " minutes ") : "";
-            var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
-            return dDisplay + hDisplay + mDisplay + sDisplay;
-        }
-
-        $(".functBtn").on("click", function(data){
-            $(".functBtn.active").removeClass("active");
-            $(this).addClass("active");
-        });
-
-        handleWindowResize();
-        $(window).on("resize", function(){
-            handleWindowResize();
-        });
-        function handleWindowResize(){
-            $("#main").css({
-                height: window.innerHeight + "px"
-            });
-        }
-
-        function removeTask(object){
-            var taskName = $(object).attr("name");
-            if (confirm("Removing " + taskName + ". Confirm?")){
-                $.ajax({
-                    url: "../../system/arsm/aecron/remove",
-                    data: {name: taskName},
-                    success: function(data){
-                        console.log(data);
-                        showRemoveSchedule();
-                    }
-                })
-            }
-        }
-
-        function newTaskUI(btn){
-            $(".view").hide();
-            $(".functBtn.active").removeClass("active");
-            $("#nsb").addClass("active");
-            $("#newTask").show();
-        }
-
-        function scheduleListAll(btn){
-            $(".view").hide();
-            $("#scheudleListAll").show();
-            $(".functBtn.active").removeClass("active");
-            $("#asb").addClass("active");
-            initScheduleFullList();
-        }
-
-        function showLogInfo(btn){
-            $(".view").hide();
-            $("#listLog").show();
-            $(".functBtn.active").removeClass("active");
-            $("#log").addClass("active");
-            initLogIndex();
-        }
-
-        function scheduleList(btn){
-            $(".view").hide();
-            $(".functBtn.active").removeClass("active");
-            $("#stb").addClass("active");
-            $("#scheduleView").show();
-            initScheduleList();
-        }
-
-        function showRemoveSchedule(btn){
-            $(".view").hide();
-            $(".functBtn.active").removeClass("active");
-            $("#rsb").addClass("active");
-            $("#removeShedule").show();
-            initRemoveScheduleList();
-        }
-
-        function initLogIndex(){
-            $("#logIndex").html("");
-            $.get("../../system/arsm/aecron/listlog", function(data){
-                if (data.error !== undefined){
-                    alert(data.error);
-                }else{
-                    $("#logIndex").append(`<option value="">Date (DD-MM-YYYY)</option>`);
-                    data.forEach(logFile => {
-                        var filename = logFile.split("/").pop();
-                        $("#logIndex").append(`<option value="${filename}">${filename}</option>`);
-                    });
-
-                }
-            })
-        }
-
-        function initRemoveScheduleList(){
-            $.ajax({
-                url: "../../system/arsm/aecron/list?listall=true",
-                data: {},
-                success: function(data){
-                    $("#removeScheduleList").html("");
-                    if (data.length == 0){
-                        $("#removeScheduleList").append(`<tr>
-                            <td colspan="4">
-                                No registered schedule.
-                            </td>
-                        </tr>`);
-                    }else{
-                        data.forEach(task => {
-                            var filter = $("#filter").val().trim();
-                            if (filter != ""){
-                                //Apply filter to the results
-                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
-                                    //Matching
-                                }else{
-                                    //Not matching
-                                    return
-                                }
-                            }
-
-                            var scriptLocation = task.ScriptFile;
-                            if (task.JobType == "function"){
-                                scriptLocation = "[Internal Function]";
-                            }
-
-                            var removebutton = `<button class="ui negative mini button" name="${task.Name}" onclick="removeTask(this);">Remove</button>`;
-                            if (task.JobType == "function"){
-                                //Cannot be removed
-                                removebutton = `[READ ONLY]`;
-                            }
-
-                            $("#removeScheduleList").append(`<tr>
-                                <td class="collapsing">
-                                    <i class="user icon"></i> ${task.Name} (${task.Creator})
-                                </td>
-                                <td>${scriptLocation}</td>
-                                <td>${task.Description}</td>
-                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
-                                <td class="right aligned collapsing">
-                                    ${removebutton}
-                                </td>
-                            </tr>`);
-                        });
-                    }
-                }
-            });
-        }
-
-        //Handle task creation
-        function handleTaskSubmition(e){
-            e.preventDefault();
-            var taskName = $("#tasknanme").val();
-            var desc = $("#desc").val();
-            var scriptPath = $("#scriptpath").val();
-            var interval = parseFloat($("#intervalvalue").val()) * parseFloat($("#intervalunit").val());
-            var baseunix = Date.now();
-            var baseTimeframe = $("#intervalbase").val();
-            switch (baseTimeframe){
-                case "now":
-                    //Round off to the nearest minute
-                    baseunix = moment().startOf('minute').unix()
-                    break;
-                case "hour":
-                    baseunix = moment().startOf('hour').unix()
-                    break;
-                case "day":
-                    baseunix = moment().startOf('day').unix()
-                    break;
-                case "month":
-                    baseunix = moment().startOf('month').unix()
-                    break;
-                 case "year":
-                    baseunix = moment().startOf('year').unix()
-                    break;
-                case "mon":
-                    baseunix = moment().startOf('week').add(1,'days').unix(); 
-                    break;
-                case "tue":
-                    baseunix = moment().startOf('week').add(2,'days').unix(); 
-                    break;
-                case "wed":
-                    baseunix = moment().startOf('week').add(3,'days').unix(); 
-                    break;
-                case "thu":
-                    baseunix = moment().startOf('week').add(4,'days').unix(); 
-                    break;
-                case "fri":
-                    baseunix = moment().startOf('week').add(5,'days').unix(); 
-                    break;
-                case "sat":
-                    baseunix = moment().startOf('week').add(6,'days').unix(); 
-                    break;
-                case "sun":
-                    baseunix = moment().startOf('week').unix(); 
-                    break;
-            }
-
-            //Create ajax request
-            $.ajax({
-                url: "../../system/arsm/aecron/add",
-                method: "POST",
-                data: {
-                    "name":taskName,
-                    "interval": interval,
-                    "base": baseunix,
-                    "desc": desc,
-                    "path": scriptPath
-                },
-                success: function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{ 
-                        //Clear all inputs
-                        $("#tasknanme").val("");
-                        $("#desc").val("");
-                        $("#scriptpath").val("");
-
-                        //Update the list and show it
-                        scheduleList();
-                    }
-                }
-            })
-            
-        }
-
-        function applyFilter(){
-            initScheduleFullList();
-            initScheduleList();
-            initRemoveScheduleList();
-        }
-
-        function loadLogFile(filename){
-            $.get("../../system/arsm/aecron/listlog?filename=" + filename, function(data){
-                $("#logPreview").text(data);
-            })
-        }
-
-        function handleFilterEnterKeydown(event){
-            if (event.which == 13){
-                event.preventDefault();
-                applyFilter();
-            }
-        }
-
-        function openFileSelector(){
-            ao_module_openFileSelector(scriptSelected, "user:/", "file", false);
-        }
-
-        function scriptSelected(filedata){
-            for (var i=0; i < filedata.length; i++){
-                var filename = filedata[i].filename;
-                var filepath = filedata[i].filepath;
-                $("#scriptpath").val(filepath);
-
-                //See if the filepath end with valid extension
-                var ext = filepath.split(".").pop();
-                if (ext == "js" || ext == "agi" || ext == "sh" || ext == "bat"){
-                    $("#extensionWarning").hide();
-                }else{
-                    $("#extensionWarning").show();
-                }
-            }
-        }
-    </script>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+	<meta name="mobile-web-app-capable" content="yes">
+	<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+    <meta charset="UTF-8">
+    <title>System Task Scheduler</title>
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <script src="../../script/jquery.min.js"></script>
+	<script src="../../script/semantic/semantic.min.js"></script>
+    <script src="../../script/ao_module.js"></script>
+    <script src="js/moment.min.js"></script>
+    <style>
+        body{
+            margin-top: 12px; 
+            padding-right: 15px;
+        }
+        .hidden{
+            display:none;
+        }
+
+        .compulsory{
+            color: red;
+            font-weight: bold;
+        }
+
+        #main{
+            overflow-y:auto;
+        }
+
+        #logPreview{
+            width: 100%;
+        }
+    </style>
+</head>
+<body>
+    <div>
+        <div class="ui stackable grid">
+            <div class="four wide column" >
+                <div class="ui vertical fluid menu">
+                    <div class="item">
+                        <img class="ui image" src="img/banner.png">
+                    </div>
+                    <a id="stb" class="active teal item functBtn" onclick="scheduleList(this);">
+                        User's Scheduled Tasks
+                    </a>
+                    <a id="asb" class="teal item functBtn" onclick="scheduleListAll(this);">
+                        All Scheduled Tasks
+                    </a>
+                    <a id="nsb" class="item functBtn" onclick="newTaskUI(this);">
+                        <i class="add icon"></i> New Scheduled Task
+                    </a>
+                    <a id="rsb" class="item functBtn" onclick="showRemoveSchedule(this);">
+                        <i class="remove icon"></i> Remove Scheduled Task
+                    </a>
+                    <div class="item">
+                        <div class="ui transparent icon input">
+                        <input type="text" id="filter" placeholder="Filter" onkeydown="handleFilterEnterKeydown(event);">
+                        <i class="search icon"></i>
+                        </div>
+                    </div>
+                    <a class="item" onclick="applyFilter();">
+                        <i class="filter icon"></i> Apply Filter
+                    </a>
+                    <a id="log" class="teal item functBtn" onclick="showLogInfo(this);">
+                        Show Logs
+                    </a>
+                </div>  
+            </div>
+            <div id="main" class="twelve wide column">
+                <div class="ui container">
+                    <br>
+                    <!-- Scheduled Task view-->
+                    <div id="scheduleView" class="view">
+                        <table class="ui celled striped table">
+                            <thead>
+                                <tr>
+                                    <th colspan="4">
+                                        Scheduled Tasks
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th>
+                                        Script
+                                    </th>
+                                    <th>
+                                        Location
+                                    </th>
+                                    <th>
+                                        Interval
+                                    </th>
+                                    <th>
+                                        Base Time
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody id="scheduleList">
+                            
+                            </tbody>
+                        </table>
+                    </div>
+
+                     <!-- All Scheudle List-->
+                     <div id="scheudleListAll" class="view" style="display:none;">
+                        <table class="ui celled striped table">
+                            <thead>
+                                <tr>
+                                    <th colspan="5">
+                                        System Wide Scheduled Tasks
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th>
+                                        Task Name (Creator)
+                                    </th>
+                                    <th>
+                                        Script Location
+                                    </th>
+                                    <th>
+                                        Description
+                                    </th>
+                                    <th>
+                                        Interval
+                                    </th>
+                                    <th>
+                                        Base Time
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody id="scheduleListAllContent">
+                            
+                            </tbody>
+                        </table>
+                    </div>
+
+                    <!-- New Scheduule view-->
+                    <div id="newTask" class="view hidden" >
+                        <div class="ui message">
+                            <div class="header">
+                                New Scheduled Task
+                            </div>
+                            <ul class="list">
+                                <li>You can add an agi script (.agi / .js) from one of your directories or</li>
+                                <li>A shell script (.bat or .sh) if you have admin privileges.</li>
+                            </ul>
+                            </div>
+                        <form class="ui form" onsubmit="handleTaskSubmition(event);">
+                            <div class="field">
+                            <label>Task Name (Max: 32 chars) <span class="compulsory">*</span></label>
+                            <input type="text" id="tasknanme" name="tasknanme" placeholder="New Task" maxlength="16" autocomplete="off">
+                            </div>
+                            <div class="field">
+                            <label>Description</label>
+                            <input type="text" id="desc" name="desc" placeholder="Description goes here"  autocomplete="off">
+                            </div>
+                            <div class="field">
+                                <label>Script Path <span class="compulsory">*</span></label>
+                                <div class="ui icon fluid input">
+                                    <input id="scriptpath" type="text" placeholder="Virtual path to the script file" autocomplete="off">
+                                    <i onclick="openFileSelector();" class="inverted circular search link icon"></i>
+                                </div>
+                                <div id="extensionWarning" style="display:none;" class="ui yellow small message">This extension is not supported by the scheduler. Please continue with your own risk.</div>
+                            </div>
+                            <div class="field">
+                                <label>Execution Interval <span class="compulsory">*</span></label>
+                                <div>
+                                    <div class="ui input" style="width: 200px;">
+                                        <input id="intervalvalue" type="number" placeholder="1" value="1">
+                                    </div>
+                                    <div class="ui selection dropdown">
+                                        <input type="hidden" id="intervalunit" name="intervalunit" value="60">
+                                        <i class="dropdown icon"></i>
+                                        <div class="default text">Unit</div>
+                                        <div class="menu">
+                                            <div class="item" data-value="60">Minutes</div>
+                                            <div class="item" data-value="3600">Hours</div>
+                                            <div class="item" data-value="86400">Days</div>
+                                            <div class="item" data-value="2628333">Months (approximate)</div>
+                                        </div>
+                                    </div>
+                                    <br>
+                                    <small>Execute the script above ever x unit. Month (Unit) is approximated as 2628333 seconds</small>
+                                </div>
+                            </div>
+                            <div class="field">
+                                <label>Execution Base <span class="compulsory">*</span></label>
+                                <div class="ui selection dropdown">
+                                    <input type="hidden" id="intervalbase" name="intervalbase" value="now">
+                                    <i class="dropdown icon"></i>
+                                    <div class="default text">Base Timeframe</div>
+                                    <div class="menu">
+                                        <div class="item" data-value="now">Now</div>
+                                        <div class="item" data-value="hour">Start of the Hour</div>
+                                        <div class="item" data-value="day">Start of the Day</div>
+                                        <div class="item" data-value="month">Start of the Month</div>
+                                        <div class="item" data-value="year">Start of the Year</div>
+                                        <div class="item" data-value="mon">Monday Mid-night</div>
+                                        <div class="item" data-value="tue">Tuesday Mid-night</div>
+                                        <div class="item" data-value="wed">Wednesday Mid-night</div>
+                                        <div class="item" data-value="thu">Thursday Mid-night</div>
+                                        <div class="item" data-value="fri">Friday Mid-night</div>
+                                        <div class="item" data-value="sat">Saturday Mid-night</div>
+                                        <div class="item" data-value="sun">Sunday Mid-night</div>
+                                    </div>
+                                </div>
+                                <small>The base day helps you to offset the interval for weekly / monthly based schedules.</small>
+                            </div>
+                            <button class="ui button" type="submit">Submit</button>
+                            <br> <br>
+                            <small>Fields with <span class="compulsory">*</span> are compulsory</small>
+                        </form>
+
+                        <div class="ui info message">
+                            <i class="close icon" onclick='$(this).parent().fadeOut();'></i>
+                            <div class="header">
+                                Tips for running shell scripts
+                            </div>
+                            <ul class="list">
+                                <li>The execution root of all bash script and batch script are based on the arozos root.</li>
+                                <li>Remember always use absolute path inside your scripts.</li>
+                            </ul>
+                        </div>
+                        <br><br>
+                    </div>
+
+                    <!-- Remove Scheduule view-->
+                    <div id="removeShedule" class="view hidden" >
+                        <table class="ui celled striped table">
+                            <thead>
+                                <tr>
+                                    <th class="ui red message" colspan="5">
+                                        <i class="remove icon"></i> Remove Scheduled Tasks<br>
+                                        <small>WARNING! All operations are not reversible.</small>
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th>
+                                        Task Name (Creator)
+                                    </th>
+                                    <th>
+                                        Script Location
+                                    </th>
+                                    <th>
+                                        Description
+                                    </th>
+                                    <th>
+                                        Interval
+                                    </th>
+                                    <th>
+                                        Action
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody id="removeScheduleList">
+                            
+                            </tbody>
+                        </table>
+                    </div>
+
+                     <!-- Scheduler Log view-->
+                     <div id="listLog" class="view hidden" >
+                        <p>Select a log file to preview</p>
+                        <select id="logIndex" class="ui search fluid dropdown" onchange="loadLogFile(this.value);">
+                            <option value="">Date (DD-MM-YYYY)</option>
+                            <option value="AL">Alabama</option>
+                        </select>
+                        <div class="ui segment form">
+                            <div class="field">
+                                <textarea id="logPreview" rows="10"></textarea>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+ 
+    <script>
+        $(".ui.dropdown").dropdown();
+        initScheduleList();
+        function initScheduleList(){
+            $.ajax({
+                url: "../../system/arsm/aecron/list",
+                data: {},
+                success: function(data){
+                    $("#scheduleList").html("");
+                    if (data.length == 0){
+                        $("#scheduleList").append(`<tr>
+                            <td colspan="4">
+                                No registered schedule.
+                            </td>
+                        </tr>`);
+                    }else{
+                        data.forEach(task => {
+                            var filter = $("#filter").val().trim();
+                            if (filter != ""){
+                                //Apply filter to the results
+                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
+                                    //Matching
+                                }else{
+                                    //Not matching
+                                    return
+                                }
+                            }
+                            var scriptName = task.ScriptVpath.split("/").pop();
+                            $("#scheduleList").append(`<tr>
+                                <td class="collapsing">
+                                    <i class="code file outline icon"></i> ${scriptName}
+                                </td>
+                                <td>${task.ScriptVpath}</td>
+                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
+                                <td class="right aligned collapsing">${moment.unix(task.BaseTime).format('LLL')}</td>
+                            </tr>`);
+                        });
+                    }
+                }
+            });
+        }
+
+        function initScheduleFullList(){
+            $.ajax({
+                url: "../../system/arsm/aecron/list?listall=true",
+                data: {},
+                success: function(data){
+                    $("#scheduleListAllContent").html("");
+                    if (data.length == 0){
+                        $("#scheduleListAllContent").append(`<tr>
+                            <td colspan="4">
+                                No registered schedule.
+                            </td>
+                        </tr>`);
+                    }else{
+                        data.forEach(task => {
+                            var filter = $("#filter").val().trim();
+                            if (filter != ""){
+                                //Apply filter to the results
+                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
+                                    //Matching
+                                }else{
+                                    //Not matching
+                                    return
+                                }
+                            }
+
+                            var scriptLocation = task.ScriptVpath;
+                            $("#scheduleListAllContent").append(`<tr>
+                                <td class="collapsing">
+                                    <i class="user icon"></i> ${task.Name} (${task.Creator})
+                                </td>
+                                <td>${scriptLocation}</td>
+                                <td>${task.Description}</td>
+                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
+                                <td class="right aligned collapsing">${moment.unix(task.BaseTime).format('LLL')}</td>
+                            </tr>`);
+                        });
+                    }
+                }
+            });
+        }
+
+        function parseSecondsToHumanReadableFormat(seconds){
+            seconds = Number(seconds);
+            var d = Math.floor(seconds / (3600*24));
+            var h = Math.floor(seconds % (3600*24) / 3600);
+            var m = Math.floor(seconds % 3600 / 60);
+            var s = Math.floor(seconds % 60);
+
+            var dDisplay = d > 0 ? d + (d == 1 ? " day " : " days ") : "";
+            var hDisplay = h > 0 ? h + (h == 1 ? " hour " : " hours ") : "";
+            var mDisplay = m > 0 ? m + (m == 1 ? " minute " : " minutes ") : "";
+            var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
+            return dDisplay + hDisplay + mDisplay + sDisplay;
+        }
+
+        $(".functBtn").on("click", function(data){
+            $(".functBtn.active").removeClass("active");
+            $(this).addClass("active");
+        });
+
+        handleWindowResize();
+        $(window).on("resize", function(){
+            handleWindowResize();
+        });
+        function handleWindowResize(){
+            $("#main").css({
+                height: window.innerHeight + "px"
+            });
+        }
+
+        function removeTask(object){
+            var taskName = $(object).attr("name");
+            if (confirm("Removing " + taskName + ". Confirm?")){
+                $.ajax({
+                    url: "../../system/arsm/aecron/remove",
+                    data: {name: taskName},
+                    success: function(data){
+                        console.log(data);
+                        showRemoveSchedule();
+                    }
+                })
+            }
+        }
+
+        function newTaskUI(btn){
+            $(".view").hide();
+            $(".functBtn.active").removeClass("active");
+            $("#nsb").addClass("active");
+            $("#newTask").show();
+        }
+
+        function scheduleListAll(btn){
+            $(".view").hide();
+            $("#scheudleListAll").show();
+            $(".functBtn.active").removeClass("active");
+            $("#asb").addClass("active");
+            initScheduleFullList();
+        }
+
+        function showLogInfo(btn){
+            $(".view").hide();
+            $("#listLog").show();
+            $(".functBtn.active").removeClass("active");
+            $("#log").addClass("active");
+            initLogIndex();
+        }
+
+        function scheduleList(btn){
+            $(".view").hide();
+            $(".functBtn.active").removeClass("active");
+            $("#stb").addClass("active");
+            $("#scheduleView").show();
+            initScheduleList();
+        }
+
+        function showRemoveSchedule(btn){
+            $(".view").hide();
+            $(".functBtn.active").removeClass("active");
+            $("#rsb").addClass("active");
+            $("#removeShedule").show();
+            initRemoveScheduleList();
+        }
+
+        function initLogIndex(){
+            $("#logIndex").html("");
+            $.get("../../system/arsm/aecron/listlog", function(data){
+                if (data.error !== undefined){
+                    alert(data.error);
+                }else{
+                    $("#logIndex").append(`<option value="">Date (DD-MM-YYYY)</option>`);
+                    data.forEach(logFile => {
+                        var filename = logFile.split("/").pop();
+                        $("#logIndex").append(`<option value="${filename}">${filename}</option>`);
+                    });
+
+                }
+            })
+        }
+
+        function initRemoveScheduleList(){
+            $.ajax({
+                url: "../../system/arsm/aecron/list?listall=true",
+                data: {},
+                success: function(data){
+                    $("#removeScheduleList").html("");
+                    if (data.length == 0){
+                        $("#removeScheduleList").append(`<tr>
+                            <td colspan="4">
+                                No registered schedule.
+                            </td>
+                        </tr>`);
+                    }else{
+                        data.forEach(task => {
+                            var filter = $("#filter").val().trim();
+                            if (filter != ""){
+                                //Apply filter to the results
+                                if (task.Creator.includes(filter) || task.Name.includes(filter) || task.Description.includes(filter)){
+                                    //Matching
+                                }else{
+                                    //Not matching
+                                    return
+                                }
+                            }
+
+                            var scriptLocation = task.ScriptVpath;
+                            var removebutton = `<button class="ui negative mini button" name="${task.Name}" onclick="removeTask(this);">Remove</button>`;
+                            
+                            $("#removeScheduleList").append(`<tr>
+                                <td class="collapsing">
+                                    <i class="user icon"></i> ${task.Name} (${task.Creator})
+                                </td>
+                                <td>${scriptLocation}</td>
+                                <td>${task.Description}</td>
+                                <td class="right aligned collapsing">Every ${parseSecondsToHumanReadableFormat(task.ExecutionInterval)}</td>
+                                <td class="right aligned collapsing">
+                                    ${removebutton}
+                                </td>
+                            </tr>`);
+                        });
+                    }
+                }
+            });
+        }
+
+        //Handle task creation
+        function handleTaskSubmition(e){
+            e.preventDefault();
+            var taskName = $("#tasknanme").val();
+            var desc = $("#desc").val();
+            var scriptPath = $("#scriptpath").val();
+            var interval = parseFloat($("#intervalvalue").val()) * parseFloat($("#intervalunit").val());
+            var baseunix = Date.now();
+            var baseTimeframe = $("#intervalbase").val();
+            switch (baseTimeframe){
+                case "now":
+                    //Round off to the nearest minute
+                    baseunix = moment().startOf('minute').unix()
+                    break;
+                case "hour":
+                    baseunix = moment().startOf('hour').unix()
+                    break;
+                case "day":
+                    baseunix = moment().startOf('day').unix()
+                    break;
+                case "month":
+                    baseunix = moment().startOf('month').unix()
+                    break;
+                 case "year":
+                    baseunix = moment().startOf('year').unix()
+                    break;
+                case "mon":
+                    baseunix = moment().startOf('week').add(1,'days').unix(); 
+                    break;
+                case "tue":
+                    baseunix = moment().startOf('week').add(2,'days').unix(); 
+                    break;
+                case "wed":
+                    baseunix = moment().startOf('week').add(3,'days').unix(); 
+                    break;
+                case "thu":
+                    baseunix = moment().startOf('week').add(4,'days').unix(); 
+                    break;
+                case "fri":
+                    baseunix = moment().startOf('week').add(5,'days').unix(); 
+                    break;
+                case "sat":
+                    baseunix = moment().startOf('week').add(6,'days').unix(); 
+                    break;
+                case "sun":
+                    baseunix = moment().startOf('week').unix(); 
+                    break;
+            }
+
+            //Create ajax request
+            $.ajax({
+                url: "../../system/arsm/aecron/add",
+                method: "POST",
+                data: {
+                    "name":taskName,
+                    "interval": interval,
+                    "base": baseunix,
+                    "desc": desc,
+                    "path": scriptPath
+                },
+                success: function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{ 
+                        //Clear all inputs
+                        $("#tasknanme").val("");
+                        $("#desc").val("");
+                        $("#scriptpath").val("");
+
+                        //Update the list and show it
+                        scheduleList();
+                    }
+                }
+            })
+            
+        }
+
+        function applyFilter(){
+            initScheduleFullList();
+            initScheduleList();
+            initRemoveScheduleList();
+        }
+
+        function loadLogFile(filename){
+            $.get("../../system/arsm/aecron/listlog?filename=" + filename, function(data){
+                $("#logPreview").text(data);
+            })
+        }
+
+        function handleFilterEnterKeydown(event){
+            if (event.which == 13){
+                event.preventDefault();
+                applyFilter();
+            }
+        }
+
+        function openFileSelector(){
+            ao_module_openFileSelector(scriptSelected, "user:/", "file", false);
+        }
+
+        function scriptSelected(filedata){
+            for (var i=0; i < filedata.length; i++){
+                var filename = filedata[i].filename;
+                var filepath = filedata[i].filepath;
+                $("#scriptpath").val(filepath);
+
+                //See if the filepath end with valid extension
+                var ext = filepath.split(".").pop();
+                if (ext == "js" || ext == "agi" || ext == "sh" || ext == "bat"){
+                    $("#extensionWarning").hide();
+                }else{
+                    $("#extensionWarning").show();
+                }
+            }
+        }
+    </script>
+</body>
 </html>