2023-03-21 19:03:06 +09:00
//-----------------------------------------------------------
// Expandable Search on Users and Machines pages
//-----------------------------------------------------------
2023-03-21 20:42:53 +09:00
function show _search ( ) {
2023-03-21 21:58:38 +09:00
$ ( '#nav-search' ) . removeClass ( 'hidden' ) ;
2023-03-21 22:11:41 +09:00
$ ( '#nav-search' ) . addClass ( 'show' ) ;
$ ( '#nav-content' ) . removeClass ( 'show' ) ;
2023-03-21 21:58:38 +09:00
$ ( '#nav-content' ) . addClass ( 'hidden' ) ;
2023-03-21 22:20:41 +09:00
}
function hide _search ( ) {
$ ( '#nav-content' ) . removeClass ( 'hidden' ) ;
$ ( '#nav-content' ) . addClass ( 'show' ) ;
2023-03-21 22:11:41 +09:00
$ ( '#nav-search' ) . removeClass ( 'show' ) ;
$ ( '#nav-search' ) . addClass ( 'hidden' ) ;
2023-03-21 20:42:53 +09:00
}
2023-03-21 20:23:53 +09:00
2023-02-06 04:58:09 +00:00
//-----------------------------------------------------------
// General Helpers
//-----------------------------------------------------------
function loading ( ) {
return ` <center>
< div class = "preloader-wrapper big active" >
< div class = "spinner-layer spinner-blue-only" >
< div class = "circle-clipper left" >
< div class = "circle" >
< / d i v >
< / d i v >
< div class = "gap-patch" >
< div class = "circle" >
< / d i v >
< / d i v >
< div class = "circle-clipper right" >
< div class = "circle" > < / d i v >
< / d i v >
< / d i v >
< / d i v >
< / c e n t e r > `
}
function get _color ( id ) {
// Define the colors... Seems like a good number to start with
var colors = [
"red lighten-1" ,
"teal lighten-1" ,
"blue lighten-1" ,
"blue-grey lighten-1" ,
"indigo lighten-2" ,
"green lighten-1" ,
"deep-orange lighten-1" ,
"yellow lighten-2" ,
"purple lighten-2" ,
"indigo lighten-2" ,
"brown lighten-1" ,
"grey lighten-1"
] ;
index = id % colors . length
return colors [ index ]
}
// Generic modal used for alerts / problems
function load _modal _generic ( type , title , message ) {
console . log ( "Loading the generic modal" )
element = document . getElementById ( 'generic_modal' )
content _element = document . getElementById ( 'generic_modal_content' )
title _element = document . getElementById ( 'generic_modal_title' )
content _element . innerHTML = loading ( )
title _element . innerHTML = "Loading..."
html = ""
switch ( type ) {
case "warning" || "Warning" :
title _html = "Warning"
content _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle yellow" > priority _high < / i >
< span class = "title" > $ { title } < / s p a n >
< p > $ { message } < / p >
< / l i >
< / u l > `
break ;
case "success" || "Success" :
title _html = "Success"
content _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle green" > check < / i >
< span class = "title" > $ { title } < / s p a n >
< p > $ { message } < / p >
< / l i >
< / u l > `
break ;
case "error" || "Error" :
title _html = "Error"
content _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > $ { title } < / s p a n >
< p > $ { message } < / p >
< / l i >
< / u l > `
break ;
case "information" || "Information" :
title _html = "Information"
content _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle grey" > help < / i >
< span class = "title" > $ { title } < / s p a n >
< p > $ { message } < / p >
< / l i >
< / u l > `
break ;
}
title _element . innerHTML = title _html
content _element . innerHTML = content _html
var instance = M . Modal . getInstance ( element ) ;
instance . open ( )
}
2023-03-17 18:19:28 +09:00
// https://stackoverflow.com/questions/3043775/how-to-escape-html#22706073
function escapeHTML ( str ) {
var p = document . createElement ( "p" ) ;
p . appendChild ( document . createTextNode ( str ) ) ;
return p . innerHTML ;
}
2023-02-06 04:58:09 +00:00
// Enables the Floating Action Button (FAB) for the Machines and Users page
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
var elems = document . querySelectorAll ( '.fixed-action-btn' ) ;
var instances = M . FloatingActionButton . init ( elems , { hoverEnabled : false } ) ;
} ) ;
// Init the date picker when adding PreAuth keys
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
var elems = document . querySelectorAll ( '.datepicker' ) ;
var instances = M . Datepicker . init ( elems ) ;
} ) ;
//-----------------------------------------------------------
// Settings Page Actions
//-----------------------------------------------------------
function test _key ( ) {
document . getElementById ( 'test_modal_results' ) . innerHTML = loading ( )
var data = $ . ajax ( {
type : "GET" ,
url : "api/test_key" ,
success : function ( response ) {
if ( response == "Unauthenticated" ) {
html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > Error < / s p a n >
< p > Key authentication failed . Check your key . < / p >
< / l i >
< / u l >
`
document . getElementById ( 'test_modal_results' ) . innerHTML = html
} else {
json = JSON . parse ( response )
var html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle green" > check < / i >
< span class = "title" > Success < / s p a n >
< p > Key authenticated with the Headscale server . < / p >
< / l i >
< / u l >
< h6 > Key Information < / h 6 >
< table class = "highlight" >
< tbody >
< tr >
< td > < b > Key ID < / b > < / t d >
< td > $ { json [ 'id' ] } < / t d >
< / t r >
< tr >
< td > < b > Prefix < / b > < / t d >
< td > $ { json [ 'prefix' ] } < / t d >
< / t r >
< tr >
< td > < b > Expiration Date < / b > < / t d >
< td > $ { json [ 'expiration' ] } < / t d >
< / t r >
< tr >
< td > < b > Creation Date < / b > < / t d >
< td > $ { json [ 'createdAt' ] } < / t d >
< / t r >
< / t b o d y >
< / t a b l e >
`
document . getElementById ( 'test_modal_results' ) . innerHTML = html
}
}
} )
}
function save _key ( ) {
var api _key = document . getElementById ( 'api_key' ) . value ;
if ( ! api _key ) {
html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > Error < / s p a n >
< p > You must enter an API key before saving . < / p >
< / l i >
< / u l >
`
document . getElementById ( 'test_modal_results' ) . innerHTML = html
return
} ;
var data = { "api_key" : api _key } ;
$ . ajax ( {
type : "POST" ,
url : "api/save_key" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
M . toast ( { html : 'Key saved. Testing...' } ) ;
test _key ( ) ;
}
} )
}
//-----------------------------------------------------------
// Modal Loaders
//-----------------------------------------------------------
function load _modal _rename _user ( user _id , old _name ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "green btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Rename"
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Rename user '" + old _name + "'?"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle" > language < / i >
< span class = "title" > Information < / s p a n >
< p > You are about to rename the user '${old_name}' < / p >
< / l i >
< / u l >
< h6 > New Name < / h 6 >
< div class = "input-field" >
< i class = "material-icons prefix" > language < / i >
< input value = '${old_name}' id = "new_user_name_form" type = "text" data - length = "32" >
< / d i v >
`
modal _body . innerHTML = body _html
$ ( document ) . ready ( function ( ) { $ ( 'input#new_user_name_form' ) . characterCounter ( ) ; } ) ;
modal _confirm . setAttribute ( 'onclick' , 'rename_user(' + user _id + ', "' + old _name + '")' )
}
function load _modal _delete _user ( user _id , user _name ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "red btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Delete"
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Delete user '" + user _name + "'?"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > Warning < / s p a n >
< p > Are you sure you want to delete the user '${user_name}' ? < / p >
< / l i >
< / u l >
`
modal _body . innerHTML = body _html
modal _confirm . setAttribute ( 'onclick' , 'delete_user("' + user _id + '", "' + user _name + '")' )
}
function load _modal _add _preauth _key ( user _name ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "green btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Add"
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Adding a PreAuth key to '" + user _name + "'"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle" > help < / i >
< span class = "title" > Information < / s p a n >
< p >
< ul >
< li > Pre - Auth keys can be used to authenticate to Headscale without manually registering a machine . Use the flag < code > -- auth - key < / c o d e > t o d o s o . < / l i >
< li > "Ephemeral" keys can be used to register devices that frequently come on and drop off the newtork ( for example , docker containers ) < / l i >
< li > Keys that are "Reusable" can be used multiple times . Keys that are "One Time Use" will expire after their first use . < / l i >
< / u l >
< / p >
< / l i >
< / u l >
< h4 > PreAuth Key Information < / h 4 >
< br >
2023-02-28 21:36:02 +09:00
< input type = "text" class = "datepicker" id = "preauth_key_expiration_date" >
2023-02-06 04:58:09 +00:00
< p >
< label >
< input type = "checkbox" class = "filled-in" id = "checkbox-reusable" / >
< span > Reusable < / s p a n >
< / l a b e l >
< / p >
< p >
< label >
< input type = "checkbox" class = "filled-in" id = "checkbox-ephemeral" / >
< span > Ephemeral < / s p a n >
< / l a b e l >
< / p >
`
modal _body . innerHTML = body _html
// Init the date picker
M . Datepicker . init ( document . querySelector ( '.datepicker' ) , { format : 'yyyy-mm-dd' } ) ;
modal _confirm . setAttribute ( 'onclick' , 'add_preauth_key("' + user _name + '")' )
}
function load _modal _expire _preauth _key ( user _name , key ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "red lighten-2 btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Expire"
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Expire PreAuth Key?"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > Warning < / s p a n >
< p > Are you sure you want to expire this key ? It will become unusable afterwards , and any machine currently using it will disconnect . < / p >
< / l i >
< / u l >
`
modal _body . innerHTML = body _html
modal _confirm . setAttribute ( 'onclick' , 'expire_preauth_key("' + user _name + '", "' + key + '")' )
}
function load _modal _move _machine ( machine _id ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "green btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Move"
var data = { "id" : machine _id }
$ . ajax ( {
type : "POST" ,
url : "api/machine_information" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( headscale ) {
$ . ajax ( {
type : "POST" ,
url : "api/get_users" ,
success : function ( response ) {
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Move machine '" + headscale . machine . givenName + "'?"
select _html = ` <h6>Select a User</h6><select id='move-select'> `
for ( let i = 0 ; i < response . users . length ; i ++ ) {
var name = response [ "users" ] [ i ] [ "name" ]
select _html = select _html + ` <option value=" ${ name } "> ${ name } </option> `
}
select _html = select _html + ` </select> `
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle" > language < / i >
< span class = "title" > Information < / s p a n >
< p > You are about to move $ { headscale . machine . givenName } to a new user . < / p >
< / l i >
< / u l > `
body _html = body _html + select _html
body _html = body _html + ` <h6>Machine Information</h6>
< table class = "highlight" >
< tbody >
< tr >
< td > < b > Machine ID < / b > < / t d >
< td > $ { headscale . machine . id } < / t d >
< / t r >
< tr >
< td > < b > Hostname < / b > < / t d >
< td > $ { headscale . machine . name } < / t d >
< / t r >
< tr >
< td > < b > User < / b > < / t d >
< td > $ { headscale . machine . user . name } < / t d >
< / t r >
< / t b o d y >
< / t a b l e >
`
modal _body . innerHTML = body _html
M . FormSelect . init ( document . querySelectorAll ( 'select' ) )
}
} )
modal _confirm . setAttribute ( 'onclick' , 'move_machine(' + machine _id + ')' )
}
} )
}
function load _modal _delete _machine ( machine _id ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "red btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Delete"
var data = { "id" : machine _id }
$ . ajax ( {
type : "POST" ,
url : "api/machine_information" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Delete machine '" + response . machine . givenName + "'?"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle red" > warning < / i >
< span class = "title" > Warning < / s p a n >
< p > Are you sure you want to delete $ { response . machine . givenName } ? < / p >
< / l i >
< / u l >
< h6 > Machine Information < / h 6 >
< table class = "highlight" >
< tbody >
< tr >
< td > < b > Machine ID < / b > < / t d >
< td > $ { response . machine . id } < / t d >
< / t r >
< tr >
< td > < b > Hostname < / b > < / t d >
< td > $ { response . machine . name } < / t d >
< / t r >
< tr >
< td > < b > User < / b > < / t d >
< td > $ { response . machine . user . name } < / t d >
< / t r >
< / t b o d y >
< / t a b l e >
`
modal _body . innerHTML = body _html
modal _confirm . setAttribute ( 'onclick' , 'delete_machine(' + machine _id + ')' )
}
} )
}
function load _modal _rename _machine ( machine _id ) {
document . getElementById ( 'modal_content' ) . innerHTML = loading ( )
document . getElementById ( 'modal_title' ) . innerHTML = "Loading..."
document . getElementById ( 'modal_confirm' ) . className = "green btn-flat white-text"
document . getElementById ( 'modal_confirm' ) . innerText = "Rename"
var data = { "id" : machine _id }
$ . ajax ( {
type : "POST" ,
url : "api/machine_information" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
modal = document . getElementById ( 'card_modal' ) ;
modal _title = document . getElementById ( 'modal_title' ) ;
modal _body = document . getElementById ( 'modal_content' ) ;
modal _confirm = document . getElementById ( 'modal_confirm' ) ;
modal _title . innerHTML = "Rename machine '" + response . machine . givenName + "'?"
body _html = `
< ul class = "collection" >
< li class = "collection-item avatar" >
< i class = "material-icons circle" > devices < / i >
< span class = "title" > Information < / s p a n >
< p > You are about to rename $ { response . machine . givenName } < / p >
< / l i >
< / u l >
< h6 > New Name < / h 6 >
< div class = "input-field" >
< input value = '${response.machine.givenName}' id = "new_name_form" type = "text" >
< label for = "new_name_form" class = "active" > New Machine Name < / l a b e l >
< / d i v >
< h6 > Machine Information < / h 6 >
< table class = "highlight" >
< tbody >
< tr >
< td > < b > Machine ID < / b > < / t d >
< td > $ { response . machine . id } < / t d >
< / t r >
< tr >
< td > < b > Hostname < / b > < / t d >
< td > $ { response . machine . name } < / t d >
< / t r >
< tr >
< td > < b > User < / b > < / t d >
< td > $ { response . machine . user . name } < / t d >
< / t r >
< / t b o d y >
< / t a b l e >
`
modal _body . innerHTML = body _html
modal _confirm . setAttribute ( 'onclick' , 'rename_machine(' + machine _id + ')' )
}
} )
}
function load _modal _add _machine ( ) {
$ . ajax ( {
type : "POST" ,
url : "api/get_users" ,
success : function ( response ) {
modal _body = document . getElementById ( 'default_add_new_machine_modal' ) ;
modal _confirm = document . getElementById ( 'new_machine_modal_confirm' ) ;
select _html = `
< div class = "col s12 m6" >
< div class = "input-field" >
< i class = "material-icons prefix" > language < / i >
< select id = 'add_machine_user_select' >
< option value = "" disabled selected > Select a User < / o p t i o n > `
for ( let i = 0 ; i < response . users . length ; i ++ ) {
var name = response [ "users" ] [ i ] [ "name" ]
select _html = select _html + ` <option value=" ${ name } "> ${ name } </option> `
}
select _html = select _html + `
< label > Select a User < / l a b e l >
< / s e l e c t >
< / d i v >
< / d i v > `
select _html = select _html + `
< div class = "col s12 m6" >
< div class = "input-field" >
< i class = "material-icons prefix" > vpn _key < / i >
< input id = "add_machine_key_field" type = "password" >
< label for = "add_machine_key_field" > Machine Registration Key < / l a b e l >
< / d i v >
< / d i v > `
for ( let i = 0 ; i < response . users . length ; i ++ ) {
var name = response [ "users" ] [ i ] [ "name" ]
select _html = select _html + ` <p><br></p> `
}
select _html = select _html + ` <p><br></p> `
select _html = select _html + ` <p><br></p> `
modal _body . innerHTML = select _html
// Initialize the form and the machine tabs
2023-03-22 08:18:36 +09:00
M . FormSelect . init ( document . querySelectorAll ( 'select' ) { classes = 'add_machine_selector_class' } )
2023-02-06 04:58:09 +00:00
M . Tabs . init ( document . getElementById ( 'new_machine_tabs' ) ) ;
}
} )
}
//-----------------------------------------------------------
// Machine Page Actions
//-----------------------------------------------------------
function delete _chip ( machine _id , chipsData ) {
// We need to get ALL the current tags -- We don't care about what's deleted, just what's remaining
// chipsData is an array generated from from the creation of the array.
chips = JSON . stringify ( chipsData )
var formattedData = [ ] ;
for ( let tag in chipsData ) {
formattedData [ tag ] = '"tag:' + chipsData [ tag ] . tag + '"'
}
var tags _list = '{"tags": [' + formattedData + ']}'
var data = { "id" : machine _id , "tags_list" : tags _list }
$ . ajax ( {
type : "POST" ,
url : "api/set_machine_tags" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
M . toast ( { html : 'Tag removed.' } ) ;
}
} )
}
function add _chip ( machine _id , chipsData ) {
chips = JSON . stringify ( chipsData ) . toLowerCase ( )
chipsData [ chipsData . length - 1 ] . tag = chipsData [ chipsData . length - 1 ] . tag . trim ( ) . replace ( /\s+/g , '-' )
last _chip _fixed = chipsData [ chipsData . length - 1 ] . tag
var formattedData = [ ] ;
for ( let tag in chipsData ) {
formattedData [ tag ] = '"tag:' + chipsData [ tag ] . tag + '"'
}
var tags _list = '{"tags": [' + formattedData + ']}'
var data = { "id" : machine _id , "tags_list" : tags _list }
$ . ajax ( {
type : "POST" ,
url : "api/set_machine_tags" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
M . toast ( { html : 'Tag "' + last _chip _fixed + '" added.' } ) ;
}
} )
}
function add _machine ( ) {
2023-02-28 21:18:59 +09:00
var key = document . getElementById ( 'add_machine_key_field' ) . value
2023-02-06 04:58:09 +00:00
var user = document . getElementById ( 'add_machine_user_select' ) . value
var data = { "key" : key , "user" : user }
if ( user == "" ) {
load _modal _generic ( "error" , "User is empty" , "Select a user before submitting" )
return
}
if ( key == "" ) {
load _modal _generic ( "error" , "Key is empty" , "Input the key generated by your <code>tailscale login</code> command" )
return
}
$ . ajax ( {
type : "POST" ,
url : "api/register_machine" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
alert ( "Response: " + response )
}
} )
}
function rename _machine ( machine _id ) {
var new _name = document . getElementById ( 'new_name_form' ) . value ;
var data = { "id" : machine _id , "new_name" : new _name } ;
// String to test against
var regexIT = /[`!@#$%^&*()_+\=\[\]{};':"\\|,.<>\/?~]/ ;
if ( regexIT . test ( new _name ) ) { load _modal _generic ( "error" , "Invalid Name" , "Name cannot contain special characters ('" + regexIT + "')" ) ; return }
// If there are characters other than - and alphanumeric, throw an error
if ( new _name . includes ( ' ' ) ) { load _modal _generic ( "error" , "Name cannot have spaces" , "Allowed characters are dashes (-) and alphanumeric characters" ) ; return }
// If it is longer than 32 characters, throw an error
if ( new _name . length > 32 ) { load _modal _generic ( "error" , "Name is too long" , "The name name is too long. Maximum length is 32 characters" ) ; return }
// If the new_name is empty, throw an error
if ( ! new _name ) { load _modal _generic ( "error" , "Name can't be empty" , "Please enter a machine name before submitting." ) ; return }
$ . ajax ( {
type : "POST" ,
url : "api/rename_machine" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
2023-03-17 18:19:28 +09:00
document . getElementById ( machine _id + '-name-container' ) . innerHTML = machine _id + ". " + escapeHTML ( new _name )
M . toast ( { html : 'Machine ' + machine _id + ' renamed to ' + escapeHTML ( new _name ) } ) ;
2023-02-06 04:58:09 +00:00
} else {
load _modal _generic ( "error" , "Error setting the machine name" , "Headscale response: " + JSON . stringify ( response . body . message ) )
}
}
} )
}
function move _machine ( machine _id ) {
new _user = document . getElementById ( 'move-select' ) . value
var data = { "id" : machine _id , "new_user" : new _user } ;
$ . ajax ( {
type : "POST" ,
url : "api/move_user" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
document . getElementById ( machine _id + '-user-container' ) . innerHTML = response . machine . user . name
document . getElementById ( machine _id + '-ns-badge' ) . innerHTML = response . machine . user . name
// Get the color and set it
var user _color = get _color ( response . machine . user . id )
document . getElementById ( machine _id + '-ns-badge' ) . className = "badge ipinfo " + user _color + " white-text hide-on-small-only"
M . toast ( { html : "'" + response . machine . givenName + "' moved to user " + response . machine . user . name } ) ;
}
} )
}
function delete _machine ( machine _id ) {
var data = { "id" : machine _id } ;
$ . ajax ( {
type : "POST" ,
url : "api/delete_machine" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// When the machine is deleted, hide its collapsible:
document . getElementById ( machine _id + '-main-collapsible' ) . className = "collapsible popout hide" ;
M . toast ( { html : 'Machine deleted.' } ) ;
}
} )
}
function toggle _route ( route _id , current _state ) {
var data = { "route_id" : route _id , "current_state" : current _state }
$ . ajax ( {
type : "POST" ,
url : "api/update_route" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
// Response is a JSON object containing the Headscale API response of /v1/api/machines/<id>/route
var element = document . getElementById ( route _id ) ;
var disabledClass = "waves-effect waves-light btn-small red lighten-2 tooltipped" ;
var enabledClass = "waves-effect waves-light btn-small green lighten-2 tooltipped" ;
var disabledTooltip = "Click to enable"
var enabledTooltip = "Click to disable"
var disableState = "False"
var enableState = "True"
var action _taken = "unchanged." ;
if ( element . className == disabledClass ) {
// 1. Change the class to change the color of the icon
// 2. Change the "action taken" for the M.toast popup
// 3. Change the tooltip to say "Click to enable/disable"
element . className = enabledClass
var action _taken = "enabled."
element . setAttribute ( 'data-tooltip' , enabledTooltip )
element . setAttribute ( 'onclick' , 'toggle_route(' + route _id + ', "' + enableState + '")' )
} else if ( element . className == enabledClass ) {
element . className = disabledClass
var action _taken = "disabled."
element . setAttribute ( 'data-tooltip' , disabledTooltip )
element . setAttribute ( 'onclick' , 'toggle_route(' + route _id + ', "' + disableState + '")' )
}
M . toast ( { html : 'Route ' + action _taken } ) ;
}
} )
}
//-----------------------------------------------------------
// Machine Page Helpers
//-----------------------------------------------------------
function btn _toggle ( state ) {
if ( state == "show" ) { document . getElementById ( 'new_machine_modal_confirm' ) . className = 'green btn-flat white-text' }
else { document . getElementById ( 'new_machine_modal_confirm' ) . className = 'green btn-flat white-text hide' }
}
//-----------------------------------------------------------
// User Page Actions
//-----------------------------------------------------------
function rename _user ( user _id , old _name ) {
var new _name = document . getElementById ( 'new_user_name_form' ) . value ;
var data = { "old_name" : old _name , "new_name" : new _name }
// String to test against
var regexIT = /[`!@#$%^&*()_+\=\[\]{};':"\\|,.<>\/?~]/ ;
if ( regexIT . test ( new _name ) ) { load _modal _generic ( "error" , "Invalid Name" , "Name cannot contain special characters ('" + regexIT + "')" ) ; return }
// If there are characters other than - and alphanumeric, throw an error
if ( new _name . includes ( ' ' ) ) { load _modal _generic ( "error" , "Name cannot have spaces" , "Allowed characters are dashes (-) and alphanumeric characters" ) ; return }
// If it is longer than 32 characters, throw an error
if ( new _name . length > 32 ) { load _modal _generic ( "error" , "Name is too long" , "The user name is too long. Maximum length is 32 characters" ) ; return }
// If the new_name is empty, throw an error
if ( ! new _name ) { load _modal _generic ( "error" , "Name can't be empty" , "The user name cannot be empty." ) ; return }
$ . ajax ( {
type : "POST" ,
url : "api/rename_user" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// Rename the user on the page:
2023-03-17 18:19:28 +09:00
document . getElementById ( user _id + '-name-span' ) . innerHTML = escapeHTML ( new _name )
2023-02-06 04:58:09 +00:00
// Set the button to use the NEW name as the OLD name for both buttons
var rename _button _sm = document . getElementById ( user _id + '-rename-user-sm' )
rename _button _sm . setAttribute ( 'onclick' , 'load_modal_rename_user(' + user _id + ', "' + new _name + '")' )
var rename _button _lg = document . getElementById ( user _id + '-rename-user-lg' )
rename _button _lg . setAttribute ( 'onclick' , 'load_modal_rename_user(' + user _id + ', "' + new _name + '")' )
// Send the completion toast
M . toast ( { html : "User '" + old _name + "' renamed to '" + new _name + "'." } )
} else {
load _modal _generic ( "error" , "Error setting user name" , "Headscale response: " + JSON . stringify ( response . body . message ) )
}
}
} )
}
function delete _user ( user _id , user _name ) {
var data = { "name" : user _name } ;
$ . ajax ( {
type : "POST" ,
url : "api/delete_user" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// When the machine is deleted, hide its collapsible:
document . getElementById ( user _id + '-main-collapsible' ) . className = "collapsible popout hide" ;
M . toast ( { html : 'User deleted.' } ) ;
} else {
// We errored. Decipher the error Headscale sent us and display it:
2023-02-06 15:01:27 +09:00
load _modal _generic ( "error" , "Error deleting user" , "Headscale response: " + JSON . stringify ( response . body . message ) )
2023-02-06 04:58:09 +00:00
}
}
} )
}
function add _user ( ) {
var user _name = document . getElementById ( 'add_user_name_field' ) . value
var data = { "name" : user _name }
$ . ajax ( {
type : "POST" ,
url : "api/add_user" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// Send the completion toast
M . toast ( { html : "User '" + user _name + "' added to Headscale. Refreshing..." } )
window . location . reload ( )
} else {
// We errored. Decipher the error Headscale sent us and display it:
load _modal _generic ( "error" , "Error adding user" , "Headscale response: " + JSON . stringify ( response . body . message ) )
}
}
} )
}
function add _preauth _key ( user _name ) {
var date = document . getElementById ( 'preauth_key_expiration_date' ) . value
var ephemeral = document . getElementById ( 'checkbox-ephemeral' ) . checked
var reusable = document . getElementById ( 'checkbox-reusable' ) . checked
var expiration = date + "T00:00:00.000Z" // Headscale format.
// If there is no date, error:
if ( ! date ) { load _modal _generic ( "error" , "Invalid Date" , "Please enter a valid date" ) ; return }
var data = { "user" : user _name , "reusable" : reusable , "ephemeral" : ephemeral , "expiration" : expiration }
$ . ajax ( {
type : "POST" ,
url : "api/add_preauth_key" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Send the completion toast
M . toast ( { html : 'PreAuth key created in user ' + user _name } )
// If this is successfull, we should reload the table and close the modal:
var user _data = { "name" : user _name }
$ . ajax ( {
type : "POST" ,
url : "api/build_preauthkey_table" ,
data : JSON . stringify ( user _data ) ,
contentType : "application/json" ,
success : function ( table _data ) {
table = document . getElementById ( user _name + '-preauth-keys-collection' )
table . innerHTML = table _data
// The tooltips need to be re-initialized afterwards:
M . Tooltip . init ( document . querySelectorAll ( '.tooltipped' ) )
}
} )
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// The tooltips need to be re-initialized afterwards:
M . Tooltip . init ( document . querySelectorAll ( '.tooltipped' ) )
} else {
load _modal _generic ( "error" , "Error adding a pre-auth key" , "Headscale response: " + JSON . stringify ( response . body . message ) )
}
}
} )
}
function expire _preauth _key ( user _name , key ) {
var data = { "user" : user _name , "key" : key }
$ . ajax ( {
type : "POST" ,
url : "api/expire_preauth_key" ,
data : JSON . stringify ( data ) ,
contentType : "application/json" ,
success : function ( response ) {
if ( response . status == "True" ) {
// Send the completion toast
2023-02-09 22:55:36 +09:00
M . toast ( { html : 'PreAuth expired in ' + user _name } )
2023-02-06 04:58:09 +00:00
// If this is successfull, we should reload the table and close the modal:
var user _data = { "name" : user _name }
$ . ajax ( {
type : "POST" ,
url : "api/build_preauthkey_table" ,
data : JSON . stringify ( user _data ) ,
contentType : "application/json" ,
success : function ( table _data ) {
table = document . getElementById ( user _name + '-preauth-keys-collection' )
table . innerHTML = table _data
// The tooltips need to be re-initialized afterwards:
M . Tooltip . init ( document . querySelectorAll ( '.tooltipped' ) )
}
} )
// Get the modal element and close it
modal _element = document . getElementById ( 'card_modal' )
M . Modal . getInstance ( modal _element ) . close ( )
// The tooltips need to be re-initialized afterwards:
M . Tooltip . init ( document . querySelectorAll ( '.tooltipped' ) )
} else {
2023-02-09 22:55:36 +09:00
load _modal _generic ( "error" , "Error expiring a pre-auth key" , "Headscale response: " + JSON . stringify ( response . body . message ) )
2023-02-06 04:58:09 +00:00
}
}
} )
}
//-----------------------------------------------------------
// User Page Helpers
//-----------------------------------------------------------
// Toggle expired items on the Users PreAuth section:
function toggle _expired ( ) {
var toggle _hide = document . getElementsByClassName ( 'expired-row' ) ;
var hidden = document . getElementsByClassName ( 'expired-row hide' ) ;
if ( hidden . length == 0 ) {
for ( var i = 0 ; i < toggle _hide . length ; i ++ ) {
toggle _hide [ i ] . className = "expired-row hide" ;
}
} else if ( hidden . length > 0 ) {
for ( var i = 0 ; i < toggle _hide . length ; i ++ ) {
toggle _hide [ i ] . className = "expired-row" ;
}
}
}
2023-02-10 14:01:31 +09:00
// Copy a PreAuth Key to the clipboard. Show only the Prefix by default
function copy _preauth _key ( key ) {
navigator . clipboard . writeText ( key ) ;
M . toast ( { html : 'PreAuth key copied to clipboard.' } )
}