Skip to content

Commit

Permalink
listing player for dashboard, player quick actions (zap, ban, kick)
Browse files Browse the repository at this point in the history
  • Loading branch information
slayernominee committed Jan 9, 2024
1 parent 052ca80 commit 0ae35b8
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 65 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A free & opensource Minecraft Dashboard to control your server
- [ ] Backend Modifiable Cors
- [ ] File Editor
- [ ] Explorer Feature: Duplicate, Download, Move
- [ ] Dashboard: Kick, Ban, Get World, Get Gamemode, Change Gamemode, List Players
- [ ] Dashboard: Get World, Get Gamemode


# Setup
Expand Down
161 changes: 98 additions & 63 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,24 @@ async function check_if_setup() {
return data
}

async function get_players() {
const data = await fetch(process.env.NEXT_PUBLIC_API_URL + '/api/list_players', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
}
}).then((res) => res.json())
return data
}

export default function Home() {

const [is_running, set_is_running] = useState(false)
const [fetched_running, set_fetched_running] = useState(false)

const [players, setPlayers] = useState([])

const [max_count, set_max_count] = useState(0)

useEffect(() => {

Expand All @@ -92,6 +106,24 @@ export default function Home() {
set_is_running(is_running)
set_fetched_running(true)
})

get_players().then((player_data) => {
setPlayers(player_data.players)
set_max_count(player_data.max)
})

setInterval(() => {
get_is_running().then((is_running) => {
set_is_running(is_running)
set_fetched_running(true)
})

get_players().then((player_data) => {
setPlayers(player_data.players)
set_max_count(player_data.max)
})
}, 5000)

}, [])

const switch_running_handler = () => {
Expand All @@ -115,6 +147,70 @@ export default function Home() {

}

const playerList = players.map((player_name, idx) =>
<TableRow key={idx}>
<TableCell className="font-medium">{ player_name }</TableCell>
<TableCell>
<Select onValueChange={(v) => exec_cmd(`gamemode ${v} ${player_name}`)}>
<SelectTrigger className="w-36 border-none outline-none focus:border-none focus:outline-none">
<SelectValue placeholder="Gamemode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="survival">Survival</SelectItem>
<SelectItem value="adventure">Adventure</SelectItem>
<SelectItem value="spectator">Spectator</SelectItem>
<SelectItem value="creative">Creative</SelectItem>
</SelectContent>
</Select>
</TableCell>
<TableCell>World</TableCell>
<TableCell className="text-center">

<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost" onClick={() => exec_cmd(`execute at ${player_name} run summon lightning_bolt ^ ^ ^`)} >
<Icon path={mdiLightningBolt} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Hit the player with a lightning bolt</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>


<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost" onClick={() => exec_cmd(`kick ${player_name}`)}>
<Icon path={mdiKarate} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Kick Player</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost" onClick={() => exec_cmd(`ban ${player_name}`)}>
<Icon path={mdiGavel} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ban Player</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>


</TableCell>
</TableRow>
);

return (
<Layout>
<h1>General</h1>
Expand All @@ -141,8 +237,7 @@ export default function Home() {
<div>
<h2>Players</h2>

<span className="flex"><Icon path={mdiCircleMedium} size={1} className="mr-3 text-green-600" /> {1} / {20} Players are online</span>

<span className="flex"><Icon path={mdiCircleMedium} size={1} className={`mr-3 ${is_running ? 'text-green-600' : 'text-red-500'}`} /> {players.length} / {max_count} Players are online</span>

<Table>
<TableCaption></TableCaption>
Expand All @@ -155,67 +250,7 @@ export default function Home() {
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">MR12345</TableCell>
<TableCell>
<Select>
<SelectTrigger className="w-36 border-none outline-none focus:border-none focus:outline-none">
<SelectValue placeholder="Gamemode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="survival">Survival</SelectItem>
<SelectItem value="adventure">Adventure</SelectItem>
<SelectItem value="spectator">Spectator</SelectItem>
<SelectItem value="creative">Creative</SelectItem>
</SelectContent>
</Select>
</TableCell>
<TableCell>Nether</TableCell>
<TableCell className="text-center">

<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost">
<Icon path={mdiLightningBolt} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Hit the player with a lightning bolt</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>


<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost">
<Icon path={mdiKarate} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Kick Player</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button variant="ghost">
<Icon path={mdiGavel} size={1} className="mr-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ban Player</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>


</TableCell>
</TableRow>
{ playerList }
</TableBody>
</Table>

Expand Down
74 changes: 73 additions & 1 deletion dash/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use actix_web::web::scope;
use serde::Serialize;
use tokio::process::Command;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use actix::{ActorContext, AsyncContext, Message, Handler, Addr, StreamHandler, Actor};
Expand All @@ -21,6 +22,7 @@ static mut INP: Option<Arc<Mutex<BufWriter<tokio::process::ChildStdin>>>> = None
static mut AUTHENTICATED_SOCKETS: Option<Arc<StdMutex<Vec<Addr<MyWs>>>>> = None;

static mut IS_RUNNING: bool = false;
static mut LAST_PLAYER_LIST_OUTPUT: Option<String> = None;

pub struct BroadcastMessage {
pub data: String,
Expand All @@ -43,7 +45,17 @@ async fn server_process() -> Result<(), Box<dyn std::error::Error>> {
let mut lines = stdout.lines();
while let Some(line) = lines.next_line().await.unwrap() {
println!("{}", line);


if line.contains("players online") {
unsafe {
if let Some(last_output) = &mut LAST_PLAYER_LIST_OUTPUT {
*last_output = line.clone();
} else {
LAST_PLAYER_LIST_OUTPUT = Some(line.clone());
}
}
}

let data = line.to_string(); // Replace with your actual data
unsafe {
if let Some(authenticated_sockets) = &AUTHENTICATED_SOCKETS {
Expand Down Expand Up @@ -213,6 +225,63 @@ async fn is_running() -> HttpResponse {
}


#[derive(Debug, Serialize)]
struct PlayerListing {
count: u32,
max: u32,
players: Vec<String>,
}

async fn get_players() -> HttpResponse {
let is_running = unsafe { IS_RUNNING };
if !is_running {
let players = PlayerListing {
count: 0,
max: 0,
players: vec![],
};
return HttpResponse::Ok().json(players);
}

cmd_handler("list".to_string());

let players: String;

tokio::time::sleep(Duration::from_millis(100)).await;

unsafe {
if let Some(last_output) = &LAST_PLAYER_LIST_OUTPUT {
players = last_output.clone().split("INFO]: There are ").collect::<Vec<&str>>()[1].to_string();
} else {
let players = PlayerListing {
count: 0,
max: 0,
players: vec![],
};
return HttpResponse::Ok().json(players);
}
}

// "0 of a max of 20 players online: "
let count = players.split(" of a max of ").collect::<Vec<&str>>()[0].parse::<u32>().unwrap();
let max = players.split(" of a max of ").collect::<Vec<&str>>()[1].split(" players online:").collect::<Vec<&str>>()[0].parse::<u32>().unwrap();
let mut players = players.split(" players online: ").collect::<Vec<&str>>()[1].split(", ").map(|s| s.to_string()).collect::<Vec<String>>();

if players.len() == 1 && players[0] == "" {
players = vec![];
}

let players = PlayerListing {
count,
max,
players,
};

println!("{:?}", players);

HttpResponse::Ok().json(players)
}

async fn switch_running() -> HttpResponse {
let is_running = unsafe { IS_RUNNING };
if is_running {
Expand Down Expand Up @@ -267,9 +336,12 @@ async fn main() -> std::io::Result<()> {
// api routes
.service(scope("/api")
.wrap(tokencheck::TokenCheck)

.route("/is_running", web::post().to(is_running))
.route("/switch_running", web::post().to(switch_running))
.route("/execute_command", web::post().to(execute_command))
.route("/list_players", web::post().to(get_players))

.service(files::get_files)
.service(files::upload)
.service(files::rename)
Expand Down

0 comments on commit 0ae35b8

Please sign in to comment.