init
This commit is contained in:
commit
cc38077527
|
@ -0,0 +1,109 @@
|
|||
# Makefile for installation of echomav_deploy on EchoPilot AI hardware
|
||||
# This is installation of mavlink-router, cockpit and network setup
|
||||
.DEFAULT_GOAL := default
|
||||
SHELL := /bin/bash
|
||||
SUDO := $(shell test $${EUID} -ne 0 && echo "sudo")
|
||||
.EXPORT_ALL_VARIABLES:
|
||||
SYSCFG = /etc/mavlink-router
|
||||
CONFIG ?= /var/local
|
||||
LIBSYSTEMD=/lib/systemd/system
|
||||
SERVICES=mavlink-router.service temperature.service
|
||||
DRY_RUN=false
|
||||
LOCAL=/usr/local
|
||||
LOCAL_SCRIPTS=temperature.sh cockpitScript.sh
|
||||
|
||||
.PHONY = enable install see uninstall static default no-static
|
||||
|
||||
default:
|
||||
@$(MAKE) --no-print-directory install
|
||||
@$(MAKE) --no-print-directory static
|
||||
|
||||
explicit-ip:
|
||||
@$(MAKE) --no-print-directory install
|
||||
@$(SUDO) ./static-network.sh -i eth0 -a $(ip)
|
||||
|
||||
no-static:
|
||||
@$(MAKE) --no-print-directory install
|
||||
|
||||
disable:
|
||||
@( for c in stop disable ; do $(SUDO) systemctl $${c} $(SERVICES) ; done ; true )
|
||||
|
||||
enable:
|
||||
@( for c in stop disable ; do $(SUDO) systemctl $${c} $(SERVICES) ; done ; true )
|
||||
@( for s in $(SERVICES) ; do $(SUDO) install -Dm644 $${s%.*}.service $(LIBSYSTEMD)/$${s%.*}.service ; done ; true )
|
||||
@if [ ! -z "$(SERVICES)" ] ; then $(SUDO) systemctl daemon-reload ; fi
|
||||
@( for s in $(SERVICES) ; do $(SUDO) systemctl enable $${s%.*} ; done ; true )
|
||||
@echo ""
|
||||
@echo "Service is installed. To run now use sudo systemctl start mavlink-router and sudo systemctl start cockpit.socket"
|
||||
@echo "Inspect output with sudo journalctl -fu mavlink-router"
|
||||
@echo ""
|
||||
|
||||
static:
|
||||
# set up static ip address on eth0
|
||||
@$(SUDO) ./static-network.sh -i eth0 -a auto
|
||||
|
||||
install:
|
||||
# install helper apps
|
||||
# @$(SUDO) rm -r /var/lib/apt/lists/*
|
||||
@$(SUDO) apt update
|
||||
@$(SUDO) apt install -y nano
|
||||
@$(SUDO) apt install -y nload
|
||||
@$(SUDO) apt install -y htop
|
||||
@$(SUDO) apt install -y picocom
|
||||
|
||||
# install mavlink-router
|
||||
@rm -rf ~/tmp/mavlink-router-source
|
||||
@git clone https://github.com/EchoMAV/mavlink-router-src ~/tmp/mavlink-router-source && cd ~/tmp/mavlink-router-source && git submodule update --init --recursive
|
||||
@$(SUDO) apt -y install git ninja-build pkg-config gcc g++ systemd
|
||||
@$(SUDO) apt -y install python3-pip
|
||||
@$(SUDO) pip3 install meson smbus
|
||||
@cd ~/tmp/mavlink-router-source && meson setup build . && $(SUDO) ninja -C build install
|
||||
|
||||
# install the config file
|
||||
@$(SUDO) mkdir -p $(SYSCFG)
|
||||
@$(SUDO) cp main.conf $(SYSCFG)/.
|
||||
|
||||
# install cockpit
|
||||
@$(SUDO) ./ensure-cockpit.sh
|
||||
|
||||
# set up cockpit files
|
||||
|
||||
@$(SUDO) rm -rf /usr/share/cockpit/general/
|
||||
@$(SUDO) mkdir /usr/share/cockpit/general/
|
||||
@$(SUDO) cp -rf ui/general/* /usr/share/cockpit/general/
|
||||
@$(SUDO) cp -rf ui/branding-ubuntu/* /usr/share/cockpit/branding/ubuntu/
|
||||
@$(SUDO) cp -rf ui/static/* /usr/share/cockpit/static/
|
||||
@$(SUDO) cp -rf ui/base1/* /usr/share/cockpit/base1/
|
||||
@[ -d $(LOCAL)/echopilot ] || $(SUDO) mkdir $(LOCAL)/echopilot
|
||||
@$(SUDO) install -Dm755 version.txt $(LOCAL)/echopilot/.
|
||||
@for s in $(LOCAL_SCRIPTS) ; do $(SUDO) install -Dm755 $${s} $(LOCAL)/echopilot/$${s} ; done
|
||||
|
||||
# stop any running services we care about
|
||||
@for c in stop disable ; do $(SUDO) systemctl $${c} $(SERVICES) ; done ; true
|
||||
|
||||
# install and enable services
|
||||
@for s in $(SERVICES) ; do $(SUDO) install -Dm644 $${s%.*}.service $(LIBSYSTEMD)/$${s%.*}.service ; done
|
||||
@if [ ! -z "$(SERVICES)" ] ; then $(SUDO) systemctl daemon-reload ; fi
|
||||
@for s in $(SERVICES) ; do $(SUDO) systemctl enable $${s%.*} ; done
|
||||
|
||||
|
||||
# set up the system permissions, stop/disable nvgetty etc
|
||||
@$(SUDO) systemctl stop nvgetty
|
||||
@$(SUDO) systemctl disable nvgetty
|
||||
@$(SUDO) usermod -aG dialout $${USER}
|
||||
@$(SUDO) usermod -aG tty $${USER}
|
||||
@echo ""
|
||||
|
||||
see:
|
||||
$(SUDO) cat $(SYSCFG)/main.conf
|
||||
|
||||
serial:
|
||||
-@$(SUDO) python3 serial_number.py 0 || true
|
||||
-@$(SUDO) python3 serial_number.py 1 || true
|
||||
|
||||
uninstall:
|
||||
@$(MAKE) --no-print-directory disable
|
||||
@( for s in $(SERVICES) ; do $(SUDO) rm $(LIBSYSTEMD)/$${s%.*}.service ; done ; true )
|
||||
@if [ ! -z "$(SERVICES)" ] ; then $(SUDO) systemctl daemon-reload ; fi
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# EchoMAV EchoPilot AI Deploy
|
||||
|
||||
This is the stnadard deployment package used on Jetson modules installed on the EchoPilot AI hardware.
|
||||
A makefile is included which will install mavlink-router, cockpit and set the device up with a static ip address.
|
||||
|
||||
- mavlink-router is an open source tool to route mavlink messages across various types of endpoints. On the EchoPilot AI, it is used to accept serial data from the autopilot and act as a client or server using either UDP or TCP for packets.
|
||||
- cockpit is web-based graphical user interface, allowing you to manage the system. As configured in this install, a MAVLink-Router configuration page allows you to use a simple to use web user interface to configure mavlink-router. Simply access the system using a web browser at http://IP_ADDRESS.
|
||||
- A shell script is included which will configure a unique static IP address for the system based on the network adapter's MAC address.
|
||||
- Other helpful applications are installed, including nano, htop and nload.
|
||||
|
||||
## Steps to install
|
||||
|
||||
1. Given a freshly flashed image, gain console access via USB, e.g. `picocom /dev/ttyUSB0 -b 115200` on Linux.
|
||||
2. Ensure that one of the EchoPilot AI's network ports is plugged into a router providing a DHCP address and Internet access.
|
||||
3. Clone and use `make` to install the software
|
||||
usage:
|
||||
```
|
||||
git clone https://github.com/echomav/echopilot_deploy.git /tmp/echopilot_deploy && cd /tmp/echopilot_deploy && make
|
||||
|
||||
# If you do not want to configure a static IP, then use make no-static
|
||||
```
|
||||
|
||||
4. Record and label the device with the static IP address generated by the script, as you will need this to access the device over the network.
|
||||
5. Using a host computer on the appropriate subnet (e.g., 10.223), access the webUI at http://IP_ADDRESS. https will also work.
|
||||
6. You may also use ssh for terminal acces, e.g. `ssh echopilot@IP_ADDRESS`
|
||||
|
||||
Using the web user interface, you can now configure mavlink-router endpoints. The most common scenario is UDP Client pushing data to the ground control system computer.
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Only used by cockpit to get some information from the filesystem
|
||||
|
||||
function ApnChange
|
||||
{
|
||||
echo "Removing existing network manager profile for attcell..."
|
||||
nmcli con delete 'attcell'
|
||||
echo "Adding network manager profile for attcell..."
|
||||
nmcli connection add type gsm ifname cdc-wdm0 con-name "attcell" apn "$1" connection.autoconnect yes
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
shift
|
||||
shift
|
||||
|
||||
case $key in
|
||||
-a)
|
||||
ApnChange $1
|
||||
exit 0
|
||||
;;
|
||||
-d)
|
||||
ls /dev/ | grep video
|
||||
exit 0
|
||||
;;
|
||||
-s)
|
||||
ls /dev/ | grep ttyTH | sed -e "s/.*/\/dev\/&/"
|
||||
exit 0
|
||||
;;
|
||||
-i)
|
||||
basename -a /sys/class/net/*
|
||||
exit 0
|
||||
;;
|
||||
-c)
|
||||
nmcli con show attcell | grep gsm.apn | cut -d ":" -f2 | xargs
|
||||
exit 0
|
||||
;;
|
||||
-v)
|
||||
cat /usr/local/echopilot/version.txt
|
||||
exit 0
|
||||
;;
|
||||
-u)
|
||||
hostname -I | awk '{print $1}' | cut -d'.' -f1,2
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
done
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
# usage:
|
||||
# ensure-cockpit.sh
|
||||
#
|
||||
# This script ensures that cockpit it installed and setup
|
||||
|
||||
DRY_RUN=false
|
||||
LOCAL=/usr/local
|
||||
SUDO=$(test ${EUID} -ne 0 && which sudo)
|
||||
|
||||
$SUDO apt-get install -y cockpit
|
||||
|
||||
# Change the port to 443/80 and restart
|
||||
|
||||
$SUDO sed -i 's/9090/443/g' /lib/systemd/system/cockpit.socket
|
||||
$SUDO sed -i '/ListenStream=80/d' /lib/systemd/system/cockpit.socket
|
||||
$SUDO sed -i '/ListenStream=443/a ListenStream=80' /lib/systemd/system/cockpit.socket
|
||||
$SUDO systemctl daemon-reload
|
||||
$SUDO systemctl restart cockpit.socket
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# EchoPilot AI mavlink-router configuration file.
|
||||
# Note, this file may be auto-generated depending on your configuration.
|
||||
|
||||
[General]
|
||||
# this section only has one instance and therefore no name
|
||||
|
||||
# Print traffic statistics to stdout
|
||||
# Default: <false>
|
||||
#ReportStats = false
|
||||
|
||||
# Logging verbosity (stderr)
|
||||
# Valid values: <error>, <warning>, <info> or <debug>
|
||||
# Default: <info>
|
||||
DebugLogLevel = info
|
||||
|
||||
# Enable de-duplication of incoming messages. If a message is received another
|
||||
# time in the configured time period (in milliseconds), it will be dropped. The
|
||||
# second message will reset the timer.
|
||||
# It should be a bit higher, than the latency of the slowest link. More than
|
||||
# 1000 ms might drop packets unintentionally though.
|
||||
# Default: 0 (de-duplication disabled)
|
||||
#DeDuplicationPeriod = 0
|
||||
|
||||
## TCP Server Endpoints
|
||||
|
||||
# Listen for TCP connections on this port. Set to 0 to disable.
|
||||
# Default: 5760
|
||||
TcpServerPort = 5760
|
||||
|
||||
##
|
||||
## UART Endpoint Configurations
|
||||
## Duplicate sections (with a different name) to create multiple endpoints
|
||||
##
|
||||
|
||||
## EchoPilot UART input. Please ensure the autopilot is configured to output MAVLink2 on SERIAL2 at 500,000 kbps. On most systems this will be /dev/ttyTHS0, but could be /dev/ttyTHS1
|
||||
## Do not change the name of the endpoint as it may interfere with the web configuration UI
|
||||
[UartEndpoint alpha]
|
||||
Device = /dev/ttyTHS0
|
||||
Baud = 500000
|
||||
|
||||
##
|
||||
## UDP Endpoint Configuration Alpha
|
||||
##
|
||||
|
||||
## send to <IP ADDRESS>:14550. This should be the IP addres of your host computer running ground control software (QGCS or Mission Planner)
|
||||
## Do not change the name of the endpoint as it may interfere with the web configuration UI
|
||||
[UdpEndpoint alpha]
|
||||
Mode = Normal
|
||||
Address = 10.223.1.10
|
||||
Port = 14550
|
||||
|
||||
##
|
||||
## TCP Client Endpoint Configuration Alpha
|
||||
##
|
||||
## Do not change the name of the endpoint as it may interfere with the web configuration UI
|
||||
#[TcpEndpoint alpha]
|
||||
#Address = 10.223.1.10
|
||||
#Port = 5760
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=mavlink-router
|
||||
After=network.target multi-user.target
|
||||
RequiresMountsFor=/etc /usr
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/mavlink-routerd
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[Allow users to manage services]
|
||||
Identity=unix-group:h31
|
||||
Action=org.freedesktop.systemd1.manage-units
|
||||
ResultActive=yes
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Use this script to create a .mender file to drop into cockpit
|
||||
dest_dir=""
|
||||
filename=""
|
||||
version=""
|
||||
machine="jetson-nano-devkit"
|
||||
|
||||
mkdir -p update-artifacts/
|
||||
|
||||
help() {
|
||||
bn= `basename $0`
|
||||
echo " Usage: $bn <options> "
|
||||
echo
|
||||
echo " options: "
|
||||
echo " -h display the Help Message"
|
||||
echo " -n update package name, README should have available names"
|
||||
echo
|
||||
echo " Example: $bn create-update.sh -n h31-proxy"
|
||||
echo
|
||||
echo
|
||||
}
|
||||
|
||||
create_h31_proxy_update() {
|
||||
|
||||
dest_dir="/usr/local/h31/h31proxy/"
|
||||
filename="bin/h31proxy.net"
|
||||
version="1.0"
|
||||
artifactname="$filename-$version"
|
||||
outputpath="update-artifacts/h31proxy.mender"
|
||||
|
||||
./scripts/single-file-artifact-gen \
|
||||
-n ${artifactname} \
|
||||
-t ${machine} \
|
||||
-d ${dest_dir} \
|
||||
-o ${outputpath} \
|
||||
${filename}
|
||||
}
|
||||
|
||||
moreoptions=1
|
||||
while [ "$moreoptions" = 1 -a $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-n) shift ; UPDATE_NAME=${1} ;;
|
||||
-h) help ; exit 3 ;;
|
||||
*) help ; exit 3 ;;
|
||||
esac
|
||||
[ "$moreoptions" = 0 ] && [ $# -gt 1 ] && help && exit 1
|
||||
[ "$moreoptions" = 1 ] && shift
|
||||
done
|
||||
|
||||
if [ $UPDATE_NAME == "h31proxy" ]
|
||||
then
|
||||
create_h31_proxy_update
|
||||
fi
|
|
@ -0,0 +1,340 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CHANNEL="stable"
|
||||
|
||||
# Each available component shall be in only one of the lists below
|
||||
AVAILABLE_COMPONENTS="\
|
||||
mender-client \
|
||||
mender-configure \
|
||||
mender-configure-demo \
|
||||
mender-configure-timezone \
|
||||
mender-connect \
|
||||
mender-monitor \
|
||||
mender-monitor-demo \
|
||||
"
|
||||
|
||||
DEFAULT_COMPONENTS="\
|
||||
mender-client \
|
||||
mender-configure \
|
||||
mender-connect \
|
||||
"
|
||||
|
||||
DEMO_COMPONENTS="\
|
||||
mender-configure-demo \
|
||||
mender-configure-timezone \
|
||||
"
|
||||
|
||||
COMMERCIAL_COMPONENTS="\
|
||||
mender-monitor \
|
||||
"
|
||||
|
||||
COMMERCIAL_DEMO_COMPONENTS="\
|
||||
mender-monitor-demo \
|
||||
"
|
||||
|
||||
SELECTED_COMPONENTS="$DEFAULT_COMPONENTS"
|
||||
DEMO="0"
|
||||
|
||||
# URL prefix from where to download commercial compoments
|
||||
MENDER_COMMERCIAL_DOWNLOAD_URL="https://downloads.customer.mender.io/content/hosted/"
|
||||
|
||||
# URL path for the actual components, formatted by version
|
||||
declare -A COMMERCIAL_COMP_TO_URL_PATH_F=(
|
||||
[mender-monitor]="mender-monitor/debian/%s/mender-monitor_%s-1_all.deb"
|
||||
[mender-monitor-demo]="mender-monitor/debian/%s/mender-monitor-demo_%s-1_all.deb"
|
||||
)
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
banner (){
|
||||
echo "
|
||||
_
|
||||
_ __ ___ ___ _ __ __| | ___ _ __
|
||||
| '_ \` _ \ / _ \ '_ \ / _\` |/ _ \ '__|
|
||||
| | | | | | __/ | | | (_| | __/ |
|
||||
|_| |_| |_|\___|_| |_|\__,_|\___|_|
|
||||
|
||||
Running the Mender installation script.
|
||||
--
|
||||
"
|
||||
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "usage: install-mender.sh [options] [component...] [-- [options-for-mender-setup] ]"
|
||||
echo ""
|
||||
echo "options: [-h, help] [-c channel] [--demo] [--commercial]"
|
||||
echo " -h, --help print this help"
|
||||
echo " -c CHANNEL channel: stable(default)|experimental"
|
||||
echo " --demo use defaults appropriate for demo"
|
||||
echo " --commercial install commercial components, requires --jwt-token"
|
||||
echo " --jwt-token TOKEN Hosted Mender JWT token"
|
||||
echo ""
|
||||
echo "If no components are specified, defaults will be installed"
|
||||
echo ""
|
||||
echo "Anything after a '--' gets passed directly to 'mender setup' command."
|
||||
echo ""
|
||||
echo "Supported components (x = installed by default):"
|
||||
for c in $AVAILABLE_COMPONENTS; do
|
||||
if echo "$DEFAULT_COMPONENTS" | egrep -q "(^| )$c( |\$)"; then
|
||||
echo -n " (x) "
|
||||
else
|
||||
echo -n " (-) "
|
||||
fi
|
||||
echo "$c"
|
||||
done
|
||||
}
|
||||
|
||||
is_known_component() {
|
||||
for known in $AVAILABLE_COMPONENTS; do
|
||||
if [ "$1" = "$known" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
local selected_components=""
|
||||
local args_copy="$@"
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-c)
|
||||
if [ -n "$2" ]; then
|
||||
CHANNEL="$2"
|
||||
shift
|
||||
else
|
||||
echo "ERROR: channel requires a non-empty option argument."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--demo)
|
||||
DEMO="1"
|
||||
SELECTED_COMPONENTS="$SELECTED_COMPONENTS $DEMO_COMPONENTS"
|
||||
;;
|
||||
--commercial)
|
||||
if [[ ! "$args_copy" == *"--jwt-token"* ]]; then
|
||||
echo "ERROR: commercial requires --jwt-token argument."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
SELECTED_COMPONENTS="$SELECTED_COMPONENTS $COMMERCIAL_COMPONENTS"
|
||||
if [[ "$args_copy" == *"--demo"* ]]; then
|
||||
SELECTED_COMPONENTS="$SELECTED_COMPONENTS $COMMERCIAL_DEMO_COMPONENTS"
|
||||
fi
|
||||
;;
|
||||
--jwt-token)
|
||||
if [ -n "$2" ]; then
|
||||
JWT_TOKEN="$2"
|
||||
shift
|
||||
else
|
||||
echo "ERROR: jwt-token requires a non-empty option argument."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
MENDER_SETUP_ARGS="$@"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
if is_known_component "$1"; then
|
||||
if echo "$COMMERCIAL_COMPONENTS" | egrep -q "(^| )$1( |\$)"; then
|
||||
if [[ ! "$args_copy" == *"--jwt-token"* ]]; then
|
||||
echo "ERROR: $1 requires --jwt-token argument."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
selected_components="$selected_components $1 "
|
||||
else
|
||||
echo "Unsupported argument: \`$1\`"
|
||||
echo "Run \`mender-install.sh -h\` for help."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [ -n "$selected_components" ]; then
|
||||
SELECTED_COMPONENTS="$selected_components"
|
||||
fi
|
||||
}
|
||||
|
||||
print_components() {
|
||||
echo " Selected components:"
|
||||
for c in $SELECTED_COMPONENTS; do
|
||||
printf "\t%s\n" "$c"
|
||||
done
|
||||
}
|
||||
|
||||
init() {
|
||||
REPO_URL=https://downloads.mender.io/repos/debian
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
echo " Detected architecture:"
|
||||
printf "\t%s\n" "$ARCH"
|
||||
|
||||
echo " Installing from channel:"
|
||||
printf "\t%s\n" "$CHANNEL"
|
||||
}
|
||||
|
||||
get_deps() {
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq --no-install-recommends \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg
|
||||
}
|
||||
|
||||
add_repo() {
|
||||
curl -fsSL $REPO_URL/gpg | apt-key add -
|
||||
|
||||
repo="deb [arch=$ARCH] $REPO_URL $CHANNEL main"
|
||||
|
||||
echo "Checking if mender sources already exist in '/etc/apt/sources.list'..."
|
||||
if grep -F "$repo" /etc/apt/sources.list; then
|
||||
echo "Removing the old mender debian source list from /etc/apt/sources.list..."
|
||||
if ! sed -i.bak -e "\,$REPO_URL,d" /etc/apt/sources.list; then
|
||||
echo "Failed to remove the existing mender debian source from '/etc/apt/sources.list'."
|
||||
echo "This probably means that there already exists a source in your sources.list."
|
||||
echo "Please remove it manually before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$repo" > /etc/apt/sources.list.d/mender.list
|
||||
}
|
||||
|
||||
do_install_open() {
|
||||
# Filter out commercial components
|
||||
local selected_components_open=""
|
||||
for c in $SELECTED_COMPONENTS; do
|
||||
if ! echo "$COMMERCIAL_COMPONENTS $COMMERCIAL_DEMO_COMPONENTS" | egrep -q "(^| )$c( |\$)"; then
|
||||
selected_components_open="$selected_components_open $c"
|
||||
fi
|
||||
done
|
||||
|
||||
# Return if no open source components selected
|
||||
if [ -z "$selected_components_open" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo " Installing open source components from APT repository"
|
||||
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
$selected_components_open
|
||||
|
||||
echo " Success! Please run \`mender setup\` to configure the client."
|
||||
}
|
||||
|
||||
do_install_commercial() {
|
||||
# Filter commercial components
|
||||
local selected_components_commercial=""
|
||||
for c in $SELECTED_COMPONENTS; do
|
||||
if echo "$COMMERCIAL_COMPONENTS $COMMERCIAL_DEMO_COMPONENTS" | egrep -q "(^| )$c( |\$)"; then
|
||||
selected_components_commercial="$selected_components_commercial $c"
|
||||
fi
|
||||
done
|
||||
|
||||
# Return if no commercial components selected
|
||||
if [ -z "$selected_components_commercial" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo " Installing commercial components from $MENDER_COMMERCIAL_DOWNLOAD_URL"
|
||||
|
||||
# Translate Debian "channel" into Mender version
|
||||
if [ "$CHANNEL" = "experimental" ]; then
|
||||
version="master"
|
||||
else
|
||||
version="latest"
|
||||
fi
|
||||
|
||||
# Download deb packages
|
||||
for c in $selected_components_commercial; do
|
||||
url="$MENDER_COMMERCIAL_DOWNLOAD_URL$(printf ${COMMERCIAL_COMP_TO_URL_PATH_F[$c]} $version $version)"
|
||||
curl -fLsS -H "Authorization: Bearer $JWT_TOKEN" -O "$url" ||
|
||||
(echo ERROR: Cannot get $c from $url; exit 1)
|
||||
done
|
||||
|
||||
# Install all of them at once and fallback to install missing dependencies
|
||||
local deb_packages_glob=$(echo $selected_components_commercial | sed -e 's/ /*.deb /g; s/$/*.deb/')
|
||||
dpkg --install $deb_packages_glob || apt-get -f -y install
|
||||
|
||||
# Check individually each package
|
||||
for c in $selected_components_commercial; do
|
||||
dpkg --status $c || (echo ERROR: $c could not be installed; exit 1)
|
||||
done
|
||||
|
||||
echo " Success!"
|
||||
}
|
||||
|
||||
do_setup_mender() {
|
||||
# Return if mender-client was not installed
|
||||
if [[ ! "$SELECTED_COMPONENTS" == *"mender-client"* ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Return if no setup options were passed
|
||||
if [ -z "$MENDER_SETUP_ARGS" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo " Setting up mender with options: $MENDER_SETUP_ARGS"
|
||||
mender setup $MENDER_SETUP_ARGS
|
||||
pidof systemd && systemctl restart mender-client
|
||||
echo " Success!"
|
||||
}
|
||||
|
||||
do_setup_addons() {
|
||||
# Setup for mender-connect
|
||||
if [[ "$SELECTED_COMPONENTS" == *"mender-connect"* ]]; then
|
||||
if [ "$DEMO" -eq 1 ]; then
|
||||
echo " Setting up mender-connect with user 'root' and shell 'bash'"
|
||||
cat > /etc/mender/mender-connect.conf << EOF
|
||||
{
|
||||
"User": "root",
|
||||
"ShellCommand": "/bin/bash"
|
||||
}
|
||||
EOF
|
||||
pidof systemd && systemctl restart mender-connect
|
||||
echo " Success!"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
do_install_missing_monitor_dirs () {
|
||||
if [[ "$SELECTED_COMPONENTS" == *"mender-monitor-demo"* ]]; then
|
||||
mkdir -p /etc/mender-monitor/monitor.d/enabled || true
|
||||
mkdir -p /etc/mender-monitor/monitor.d/available || true
|
||||
fi
|
||||
}
|
||||
|
||||
banner
|
||||
init "$@"
|
||||
print_components
|
||||
get_deps
|
||||
add_repo
|
||||
do_install_open
|
||||
do_install_commercial
|
||||
do_setup_mender
|
||||
do_setup_addons
|
||||
do_install_missing_monitor_dirs
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,197 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
|
||||
Simple tool to generate Mender Artifact suitable for single-file Update Module
|
||||
|
||||
Usage: $0 [options] file [-- [options-for-mender-artifact] ]
|
||||
|
||||
Options: [ -n|artifact-name -t|--device-type -d|--dest-dir --software-name --software-version --software-filesystem -o|--output_path -h|--help ]
|
||||
|
||||
--artifact-name - Artifact name
|
||||
--device-type - Target device type identification (can be given more than once)
|
||||
--dest-dir - Target destination directory where to deploy the update
|
||||
--software-name - Name of the key to store the software version: rootfs-image.NAME.version,
|
||||
instead of rootfs-image.single-file.version
|
||||
--software-version - Value for the software version, defaults to the name of the artifact
|
||||
--software-filesystem - If specified, is used instead of rootfs-image
|
||||
--output-path - Path to output file. Default: file-install-artifact.mender
|
||||
--help - Show help and exit
|
||||
file - Single file to bundle in the update
|
||||
|
||||
Anything after a '--' gets passed directly to the mender-artifact tool.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
show_help_and_exit_error() {
|
||||
show_help
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_dependency() {
|
||||
if ! which "$1" > /dev/null; then
|
||||
echo "The $1 utility is not found but required to generate Artifacts." 1>&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! check_dependency mender-artifact; then
|
||||
echo "Please follow the instructions here to install mender-artifact and then try again: https://docs.mender.io/downloads#mender-artifact" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
device_types=""
|
||||
artifact_name=""
|
||||
dest_dir=""
|
||||
output_path="single-file-artifact.mender"
|
||||
file=""
|
||||
passthrough_args=""
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
case "$1" in
|
||||
--device-type | -t)
|
||||
if [ -z "$2" ]; then
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
device_types="$device_types $1 $2"
|
||||
shift 2
|
||||
;;
|
||||
--artifact-name | -n)
|
||||
if [ -z "$2" ]; then
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
artifact_name=$2
|
||||
shift 2
|
||||
;;
|
||||
--dest-dir | -d)
|
||||
if [ -z "$2" ]; then
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
dest_dir=$2
|
||||
shift 2
|
||||
;;
|
||||
--software-name | --software-version | --software-filesystem)
|
||||
if [ -z "$2" ]; then
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
passthrough_args="$passthrough_args $1 $2"
|
||||
shift 2
|
||||
;;
|
||||
--output-path | -o)
|
||||
if [ -z "$2" ]; then
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
output_path=$2
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
passthrough_args="$@"
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
echo "Error: unsupported option $1"
|
||||
show_help_and_exit_error
|
||||
;;
|
||||
*)
|
||||
if [ -n "$file" ]; then
|
||||
echo "File already specified. Unrecognized argument \"$1\""
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
file="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "${artifact_name}" ]; then
|
||||
echo "Artifact name not specified. Aborting."
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
|
||||
if [ -z "${device_types}" ]; then
|
||||
echo "Device type not specified. Aborting."
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
|
||||
if [ -z "${dest_dir}" ]; then
|
||||
echo "Destination dir not specified. Aborting."
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
|
||||
if [ -z "${file}" ]; then
|
||||
echo "File not specified. Aborting."
|
||||
show_help_and_exit_error
|
||||
fi
|
||||
|
||||
# Check dest-dir is an absolute path
|
||||
case $dest_dir in
|
||||
/*)
|
||||
;;
|
||||
*)
|
||||
echo "Destination dir must be an absolute path. Aborting"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create tarball, accepts single file
|
||||
filename=""
|
||||
if [ -e "${file}" ]; then
|
||||
if [ -f "${file}" ]; then
|
||||
filename=$(basename $file)
|
||||
else
|
||||
echo "Error: \"${file}\" is not a regular file. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: File \"${file}\" does not exist. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create required files for the Update Module
|
||||
tmpdir=$(mktemp -d)
|
||||
dest_dir_file="$tmpdir/dest_dir"
|
||||
filename_file="$tmpdir/filename"
|
||||
permissions_file="$tmpdir/permissions"
|
||||
|
||||
# Create dest_dir file in plain text
|
||||
echo "$dest_dir" > $dest_dir_file
|
||||
|
||||
# Create single_file file in plain text
|
||||
echo "$filename" > $filename_file
|
||||
|
||||
STAT_HAS_f=1;
|
||||
stat -f %A "${file}" >/dev/null 2>&1 || STAT_HAS_f=0;
|
||||
|
||||
# Create permissions file in plain text
|
||||
if [ $STAT_HAS_f -eq 1 ]; then
|
||||
stat -f %A "${file}" > $permissions_file
|
||||
else
|
||||
stat -c %a "${file}" > $permissions_file
|
||||
fi
|
||||
|
||||
mender-artifact write module-image \
|
||||
-T single-file \
|
||||
$device_types \
|
||||
-o "$output_path" \
|
||||
-n "$artifact_name" \
|
||||
-f "$dest_dir_file" \
|
||||
-f "$filename_file" \
|
||||
-f "$permissions_file" \
|
||||
-f "$file" \
|
||||
$passthrough_args
|
||||
|
||||
rm -rf $tmpdir
|
||||
|
||||
echo "Artifact $output_path generated successfully:"
|
||||
mender-artifact read $output_path
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,30 @@
|
|||
import smbus
|
||||
import sys
|
||||
|
||||
# usage, pass the i2c bus as the first argument, e.g. python3 serial_number 0
|
||||
|
||||
i2c_ch = int(sys.argv[1])
|
||||
|
||||
# address on the I2C bus
|
||||
i2c_address = 0x58
|
||||
|
||||
# Register address
|
||||
serial_num = 0x80
|
||||
|
||||
# Read serial number register
|
||||
def read_serial():
|
||||
|
||||
# Read the serial register, a 16 byte block
|
||||
val = bus.read_i2c_block_data(i2c_address, serial_num, 16)
|
||||
return val
|
||||
|
||||
# Initialize I2C (SMBus)
|
||||
bus = smbus.SMBus(i2c_ch)
|
||||
|
||||
try:
|
||||
# Print out the serial number
|
||||
print(bytes(read_serial()).hex())
|
||||
|
||||
except:
|
||||
pass
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
#!/bin/bash
|
||||
# EchoMAV, LLC
|
||||
# This script sets up a static network on the EchoPilot AI using NetworkManager (nmcli)
|
||||
# usage: static-network.sh -i {interface} -a {ip_addres|auto|dhcp} -g {gateway(optional)}
|
||||
# If auto is used, the static IP address will be set to 10.223.x.y where x and y are the last two octects of the network interface mac address
|
||||
# The first two octets cab be changed per IP_PREFIX
|
||||
# An alias is also added to the interface with the value of BACKDOOR_ADDR
|
||||
|
||||
IP_PREFIX="10.223"
|
||||
BACKDOOR_ADDR="192.168.154.0/24"
|
||||
sigterm_handler() {
|
||||
echo "Shutdown signal received."
|
||||
exit 1
|
||||
}
|
||||
|
||||
## Setup signal trap
|
||||
trap 'trap " " SIGINT SIGTERM SIGHUP; kill 0; wait; sigterm_handler' SIGINT SIGTERM SIGHUP
|
||||
|
||||
SUDO=$(test ${EUID} -ne 0 && which sudo)
|
||||
|
||||
function print_help { echo "Usage: ./static-network.sh -i interface_name -a ip_addres|auto -g gateway_address(optional)" >&2 ; }
|
||||
|
||||
# process args
|
||||
while getopts :i:a:g:h: flag;
|
||||
do
|
||||
case "${flag}" in
|
||||
h) print_help
|
||||
exit 1
|
||||
;;
|
||||
i) IFACE=${OPTARG};;
|
||||
a) IP_INPUT=${OPTARG};;
|
||||
g) GATEWAY=${OPTARG};;
|
||||
*) print_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# check mandatory arguments
|
||||
|
||||
if [ -z $IP_INPUT ]; then
|
||||
echo 'Missing mandatory -a argument' >&2
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z $IFACE ]; then
|
||||
echo 'Missing mandatory -i argument' >&2
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $IP_INPUT = "auto" ]; then
|
||||
ifconfig ${IFACE} &> /dev/null
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "ERROR: Failed to get information for interface ${IFACE}, does it really exist?"
|
||||
echo ""
|
||||
echo "Here is output of ip link show:"
|
||||
ip link show
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Determining the auto static IP address for interface ${IFACE}...";
|
||||
# Get the mac address
|
||||
MAC_ADDRESS=$(ifconfig ${IFACE} | awk '/ether/ {print $2}')
|
||||
|
||||
OCT1DEC=$((0x`ifconfig ${IFACE} | awk '/ether/ {print $2}' | awk '{split($0,a,"[:]"); print a[5]}'`))
|
||||
OCT2DEC=$((0x`ifconfig ${IFACE} | awk '/ether/ {print $2}' | awk '{split($0,a,"[:]"); print a[6]}'`))
|
||||
|
||||
echo "MAC address for ${IFACE} is $MAC_ADDRESS";
|
||||
|
||||
if ! [[ $OCT1DEC =~ ^[0-9]{1,3} && $OCT2DEC =~ ^[0-9]{1,3} ]] ; then
|
||||
echo "Error: Failure calculating the target IP address" >&2; exit 1
|
||||
fi
|
||||
|
||||
HOST="$IP_PREFIX.$OCT1DEC.$OCT2DEC";
|
||||
NETMASK=16;
|
||||
echo "Auto-calculated IP is $HOST/$NETMASK";
|
||||
|
||||
elif [ $IP_INPUT = "dhcp" ]; then
|
||||
#if static-iface exists, then mod to dhcp
|
||||
exist=$(nmcli c show "static-$IFACE" 2>/dev/null)
|
||||
if [ ! -z "$exist" ] ; then # delete the interface if it exists
|
||||
$SUDO nmcli con mod "static-$IFACE" ipv4.method auto
|
||||
$SUDO nmcli con mod "static-$IFACE" ipv4.gateway ""
|
||||
$SUDO nmcli con mod "static-$IFACE" ipv4.addresses ""
|
||||
$SUDO nmcli con down "static-$IFACE"
|
||||
$SUDO nmcli con up "static-$IFACE"
|
||||
else
|
||||
echo "Error: connection static-$IFACE is not found. This script is only designed to convert an existing static-$IFACE to DHCP";
|
||||
fi
|
||||
echo "Connection static-$IFACE is now set to DHCP";
|
||||
exit
|
||||
|
||||
else
|
||||
# validate ip address
|
||||
if [[ ! $IP_INPUT =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,3}$ ]]; then
|
||||
echo "ERROR! Invalid IP Address, should be x.x.x.x/y where y is the subnet mask" >&2; exit 1
|
||||
fi
|
||||
HOST=$(echo ${IP_INPUT} | cut -d/ -f 1);
|
||||
NETMASK=$(echo ${IP_INPUT} | cut -d/ -f 2);
|
||||
|
||||
echo "Configuring ${IFACE} with the provided static IP address ${HOST}/${NETMASK}";
|
||||
|
||||
fi
|
||||
|
||||
# check if there is a connection called Wired connection 1", if so take it down and delete\
|
||||
|
||||
state=$(nmcli -f GENERAL.STATE c show "Wired connection 1" 2>/dev/null)
|
||||
if [[ "$state" == *activated* ]] ; then # take the interface down
|
||||
$SUDO nmcli c down "Wired connection 1"
|
||||
fi
|
||||
exist=$(nmcli c show "Wired connection 1" 2>/dev/null)
|
||||
if [ ! -z "$exist" ] ; then # delete the interface if it exists
|
||||
echo "Removing Wired connection 1..."
|
||||
$SUDO nmcli c delete "Wired connection 1"
|
||||
fi
|
||||
|
||||
# check if there is already an interface called static-$IFACE, if so take down and delete
|
||||
state=$(nmcli -f GENERAL.STATE c show "static-$IFACE" 2>/dev/null)
|
||||
if [[ "$state" == *activated* ]] ; then # take the interface down
|
||||
$SUDO nmcli c down "static-$IFACE"
|
||||
fi
|
||||
exist=$(nmcli c show "static-$IFACE" 2>/dev/null)
|
||||
if [ ! -z "$exist" ] ; then # delete the interface if it exists
|
||||
$SUDO nmcli c delete "static-$IFACE"
|
||||
fi
|
||||
|
||||
echo "Creating new connection static-$IFACE..."
|
||||
$SUDO nmcli c add con-name "static-$IFACE" ifname $IFACE type ethernet ip4 $HOST/$NETMASK
|
||||
|
||||
# if gateway was provided, add that info to the connection
|
||||
if [[ "$GATEWAY" == *.* ]]
|
||||
then
|
||||
echo "Defining gateway ${GATEWAY}...";
|
||||
$SUDO nmcli c mod "static-$IFACE" ifname $IFACE gw4 $GATEWAY
|
||||
fi
|
||||
|
||||
# add backdoor ip address
|
||||
$SUDO nmcli c mod "static-$IFACE" +ipv4.addresses "$BACKDOOR_ADDR"
|
||||
|
||||
# bring up the interface
|
||||
$SUDO nmcli c up "static-$IFACE"
|
||||
|
||||
echo "";
|
||||
echo "Static Ethernet Configuration Successful! Interface $IFACE is set to $HOST/$NETMASK"
|
||||
echo ""
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Monitor System Temperature
|
||||
After=systemd-remount-fs.service
|
||||
RequiresMountsFor=/usr
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/echopilot/temperature.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
StartLimitInterval=300
|
||||
StartLimitBurst=5
|
||||
TimeoutStartSec=0
|
||||
TimeoutStopSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
LOG=/tmp/temperature.csv
|
||||
PERIOD=10
|
||||
THERMAL=/sys/devices/virtual/thermal/
|
||||
n=0
|
||||
echo "Logging to $LOG, $PERIOD sec period"
|
||||
echo -n "date/time," >> $LOG
|
||||
for d in $THERMAL/thermal_zone* ; do
|
||||
echo -n "zone$n," | tee -a $LOG
|
||||
n=$(($n + 1))
|
||||
done
|
||||
echo "" | tee -a $LOG
|
||||
|
||||
while true ; do
|
||||
echo -n "$(date --iso-8601=seconds)," >> $LOG
|
||||
for d in $THERMAL/thermal_zone* ; do
|
||||
x=$(cat $d/temp)
|
||||
echo -n "$x," | tee -a $LOG
|
||||
done
|
||||
echo "" | tee -a $LOG
|
||||
sleep $PERIOD
|
||||
done
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,30 @@
|
|||
body.login-pf {
|
||||
background: url("bg-plain.jpg") no-repeat 50% 0;
|
||||
background-size: auto;
|
||||
background-color: #101010;
|
||||
}
|
||||
|
||||
#badge {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
background-image: url("logo.png");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#brand {
|
||||
font-size: 18pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#brand:before {
|
||||
content: "EchoPilot AI";
|
||||
}
|
||||
|
||||
#index-brand {
|
||||
content: "EchoPilot AI";
|
||||
}
|
||||
|
||||
#index-brand:before {
|
||||
content: "EchoPilot AI";
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
|
@ -0,0 +1,92 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>General Configuration</title>
|
||||
<meta charset="utf-8">
|
||||
<link href="../base1/bootstrap.min.css" type="text/css" rel="stylesheet">
|
||||
<script src="../base1/cockpit.js"></script>
|
||||
<script src="../base1/jquery-3.7.1.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="pf-c-page">
|
||||
<main class="pf-c-page__main" tabindex="-1">
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-lg-12 ms-3"><p class="fw-bold fs-2 mt-3 mb-0">MAVLink Telemetry Configuration</p> </div>
|
||||
<div class="col-lg-12 ms-3"><p class="mt-0 mb-0">Software Version: <I><span id='version'></span></I>, Configuration File: <I><span id='file_location'></span></I></p> </div>
|
||||
<div class="col-lg-12 ms-3"><p class="mt-1 me-3">Mavlink-router provides network connectivity to/from the autopilot onboard the EchoPilot AI. The settings below can be used to configure the serial port receiving data from the autopilot as well as endpoints on the network. By default, a TCP server is running on port 5760 of the EchoPilot AI. Additional endpoints can be enabled and configured below.</p></div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col ms-3"><p class="fw-bold fs-5 mt-3 mb-0"><img src="settings_ethernet.svg" class="me-2">FMU Setup</p><p class="fw-light fs-6">Please select the serial device and baud rate used for the input of serial telemetry into the Jetson module. Default baud rate is 500000.</p></div>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3"><label class="control-label" for="fmuDevice">Linux serial device</label></div>
|
||||
<div class="col"><select id="fmuDevice"></select></div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="baudrate">Baud Rate (bps)</label></div>
|
||||
<div class="col mt-3"><select id="baudrate"></select></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col ms-3"><p class="fw-bold fs-5 mt-4 mb-0"><img src="lan.svg" class="me-2">Network UDP Telemetry Endpoint</p><p class="fw-light fs-6">Configure the endpoint used for UDP telemetry. Based on the current IP address of the EchoPilot AI, this endpoint should be in the <strong><span id='ipsubnet1'></span></strong> subnet.</p>
|
||||
<p class="fw-light fs-6">In Normal (client) mode, the EchoPilot AI will send messages to the target endpoint below. When a Ground Control System (GCS) detects these messages, it will start communicating back. Using Normal/Client Mode, the GCS is listening for a connection and the EchoPilot AI initiates the data flow. Most GCS software listens by default on port 14550 for UDP packets.</p>
|
||||
<p class="fw-light fs-6">In Server mode, the endpoint below is configured to listen for messages from a GCS. Once data is received from a GCS, the EchoPilot AI will begin sending telemetry back to it. In this configuration, the EchoPilot AI does not know about the GCS until the GCS first starts sending packets.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3"><label class="control-label" for="udpStatus">Status</label></div>
|
||||
<div class="col"><select id="udpStatus"></select></div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="udpMode">Mode</label></div>
|
||||
<div class="col mt-3"><select id="udpMode"></select></div>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="losHost">Host</label></div>
|
||||
<div class="col mt-3"><input id="losHost"></div>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="losPort">Port</label></div>
|
||||
<div class="col mt-3"><input id="losPort"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col ms-3"><p class="fw-bold fs-5 mt-4 mb-0"><img src="lan.svg" class="me-2">Network TCP Telemetry Endpoint</p>
|
||||
<p class="fw-light fs-6">Configure the endpoint used for TCP client telemetry. Based on the current IP address of the EchoPilot AI, this endpoint should be in the <strong><span id='ipsubnet2'></span></strong> subnet.</p>
|
||||
<p class="fw-light fs-6">The EchoPilot AI will send messages to the target endpoint by establishing a TCP connection to the endpoint below. When a GCS detects this connection, it will start communicating back on the socket. The GCS does not know about the EchoPilot AI until the client establishes a connection.</p>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3"><label class="control-label" for="tcpStatus">Status</label></div>
|
||||
<div class="col"><select id="tcpStatus"></select></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="tcpHost">Host</label></div>
|
||||
<div class="col mt-3"><input id="tcpHost"></div>
|
||||
</div>
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><label class="control-label" for="tcpPort">Port</label></div>
|
||||
<div class="col mt-3"><input id="tcpPort"></div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-2 ms-3 mt-3"><button class="btn-primary" id="save">Save/Update</button></div>
|
||||
<div class="col mt-3"><span id="result"></span></div>
|
||||
</div>
|
||||
|
||||
|
||||
<pre id="output"></pre>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="general.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,401 @@
|
|||
const scriptLocation = "/usr/local/echopilot/"
|
||||
const confLocation = "/etc/mavlink-router/"
|
||||
const version = document.getElementById("version");
|
||||
const file_location = document.getElementById("file_location");
|
||||
const losHost = document.getElementById("losHost");
|
||||
const losPort = document.getElementById("losPort");
|
||||
const tcpHost = document.getElementById("tcpHost");
|
||||
const tcpPort = document.getElementById("tcpPort");
|
||||
|
||||
const losIface = document.getElementById("losIface");
|
||||
const backupHost = document.getElementById("backupHost");
|
||||
const backupPort = document.getElementById("backupPort");
|
||||
const backupIface = document.getElementById("backupIface");
|
||||
const fmuDevice = document.getElementById("fmuDevice");
|
||||
const baudrate = document.getElementById("baudrate");
|
||||
const fmuId = document.getElementById("fmuId");
|
||||
const atakHost = document.getElementById("atakHost");
|
||||
const atakPort = document.getElementById("atakPort");
|
||||
const atakPeriod = document.getElementById("atakPeriod");
|
||||
const CONFIG_LENGTH = 11;
|
||||
// standard Baud rates
|
||||
const baudRateArray = [ 38400, 57600, 115200, 230400, 460800, 500000, 921600 ];
|
||||
const udpModeArray = [ "Normal", "Server"];
|
||||
const udpStatusArray = [ "Enabled", "Disabled"];
|
||||
const tcpStatusArray = [ "Enabled", "Disabled"];
|
||||
const atakPeriodArray = [ "Disabled", "1", "3", "5", "10" ];
|
||||
|
||||
var isUdpEnabled = "Disabled";
|
||||
var istcpEnabled = "Disabled";
|
||||
|
||||
enabled = true;
|
||||
// Runs the initPage when the document is loaded
|
||||
document.onload = InitPage();
|
||||
// Save file button
|
||||
document.getElementById("save").addEventListener("click", SaveSettings);
|
||||
|
||||
|
||||
|
||||
// This attempts to read the conf file, if it exists, then it will parse it and fill out the table
|
||||
// if it fails then the values are loaded with defaults.
|
||||
function InitPage() {
|
||||
cockpit.script(scriptLocation + "cockpitScript.sh -v")
|
||||
.then((content) => version.innerHTML=content)
|
||||
.catch(error => Fail(error));
|
||||
cockpit.script(scriptLocation + "cockpitScript.sh -u")
|
||||
.then(function(content) {
|
||||
ipsubnet1.innerHTML=content;
|
||||
ipsubnet2.innerHTML=content;
|
||||
})
|
||||
.catch(error => Fail(error));
|
||||
|
||||
file_location.innerHTML = confLocation + "main.conf";
|
||||
|
||||
cockpit.file(confLocation + "main.conf")
|
||||
.read().then((content, tag) => SuccessReadFile(content))
|
||||
.catch(error => FailureReadFile(error));
|
||||
|
||||
}
|
||||
|
||||
function getValueByKey(text, sectionName, sectionLength, key){
|
||||
//function to extract key values from a file with sections
|
||||
//this should work if the section is commented out or not
|
||||
var lines = text.split("\n");
|
||||
var sectionBody = "";
|
||||
for(let t = 0; t < lines.length; t++){
|
||||
if (lines[t] === sectionName || lines[t] === "#" + sectionName)
|
||||
{
|
||||
for(let n = 0; n < sectionLength; n++){
|
||||
if (lines[t].startsWith("#"))
|
||||
{
|
||||
sectionBody += lines[t+n+1].slice(1) + "\n";
|
||||
}
|
||||
else
|
||||
sectionBody += lines[t+n+1] + "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sectionBody === "")
|
||||
return null;
|
||||
var regex = new RegExp("^" + key + " =(.*)$", "m");
|
||||
var match = regex.exec(sectionBody);
|
||||
if(match)
|
||||
return match[1].trim();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSectionEnabled(text, sectionName)
|
||||
{
|
||||
var lines = text.split("\n");
|
||||
|
||||
for(let t = 0; t < lines.length; t++){
|
||||
if (lines[t] === sectionName)
|
||||
{
|
||||
if (!lines[t].startsWith("#"))
|
||||
{
|
||||
return "Enabled";
|
||||
}
|
||||
return "Disabled";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "Disabled";
|
||||
}
|
||||
|
||||
function setSectionEnabled(text, sectionName, sectionLength, enabled)
|
||||
{
|
||||
|
||||
//if enabled, find the section and remove leading #
|
||||
//if disabled, find the section and add leading #
|
||||
//return content
|
||||
var lines = text.split("\n");
|
||||
if (enabled)
|
||||
{
|
||||
//do action to enable the section
|
||||
for(let t = 0; t < lines.length; t++){
|
||||
if (lines[t] === "#" + sectionName)
|
||||
{
|
||||
for(let n = 0; n < sectionLength+1; n++){
|
||||
if (lines[t+n].startsWith("#"))
|
||||
{
|
||||
lines[t+n] = lines[t+n].slice(1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//do action to disable the section
|
||||
for(let t = 0; t < lines.length; t++){
|
||||
if (lines[t] === sectionName)
|
||||
{
|
||||
for(let n = 0; n < sectionLength+1; n++){
|
||||
lines[t+n] = "#" + lines[t+n];
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function setValueByKey(text, sectionName, sectionLength, key, value)
|
||||
{
|
||||
//find the sectionName in the text, then replace the key value with the one specified
|
||||
//return the new contents
|
||||
|
||||
//skip until we find section, then look forward X lines for the key/value
|
||||
|
||||
var lines = text.split("\n");
|
||||
|
||||
for(let t = 0; t < lines.length; t++){
|
||||
if (lines[t] === sectionName)
|
||||
{
|
||||
for(let n = 0; n < sectionLength; n++){
|
||||
if (lines[t+n+1].split('=')[0].trim() == key)
|
||||
{
|
||||
lines[t+n+1] = key + " = " + value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
|
||||
|
||||
}
|
||||
|
||||
function SuccessReadFile(content) {
|
||||
try{
|
||||
|
||||
//FMU Endpoint
|
||||
var currentfmuDevice = getValueByKey(content, "[UartEndpoint alpha]", 2, "Device");
|
||||
var currentbaudRate = getValueByKey(content, "[UartEndpoint alpha]", 2, "Baud");
|
||||
cockpit.script(scriptLocation + "cockpitScript.sh -s")
|
||||
.then((content) => AddDropDown(fmuDevice, content.split("\n"), currentfmuDevice))
|
||||
.catch(error => Fail(error));
|
||||
AddDropDown(baudrate, baudRateArray, currentbaudRate);
|
||||
|
||||
//UDP Telemetry
|
||||
isUdpEnabled = isSectionEnabled(content, "[UdpEndpoint alpha]");
|
||||
|
||||
//to do, if disabled, gray out the values
|
||||
var currentudpMode = getValueByKey(content, "[UdpEndpoint alpha]", 3, "Mode");
|
||||
losHost.value = getValueByKey(content, "[UdpEndpoint alpha]", 3, "Address");
|
||||
losPort.value = getValueByKey(content, "[UdpEndpoint alpha]", 3, "Port");
|
||||
|
||||
$(udpStatus).change(function() {
|
||||
if ($(this).val() === null)
|
||||
return;
|
||||
if ($(this).val() == "Disabled") {
|
||||
isUdpEnabled = "Disabled";
|
||||
$(udpMode).attr("disabled", "disabled");
|
||||
$(losHost).attr("disabled", "disabled");
|
||||
$(losPort).attr("disabled", "disabled");
|
||||
} else {
|
||||
isUdpEnabled = "Enabled";
|
||||
$(udpMode).removeAttr("disabled");
|
||||
$(losHost).removeAttr("disabled");
|
||||
$(losPort).removeAttr("disabled");
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
|
||||
|
||||
AddDropDown(udpMode, udpModeArray, currentudpMode);
|
||||
AddDropDown(udpStatus, udpStatusArray, isUdpEnabled);
|
||||
|
||||
// init state of UDP fields
|
||||
if (isUdpEnabled === "Disabled")
|
||||
{
|
||||
$(udpMode).attr("disabled", "disabled");
|
||||
$(losHost).attr("disabled", "disabled");
|
||||
$(losPort).attr("disabled", "disabled");
|
||||
}
|
||||
|
||||
//TCP Telemetry
|
||||
istcpEnabled = isSectionEnabled(content, "[TcpEndpoint alpha]");
|
||||
|
||||
tcpHost.value = getValueByKey(content, "[TcpEndpoint alpha]", 3, "Address");
|
||||
tcpPort.value = getValueByKey(content, "[TcpEndpoint alpha]", 3, "Port");
|
||||
|
||||
|
||||
$(tcpStatus).change(function() {
|
||||
if ($(this).val() === null)
|
||||
return;
|
||||
if ($(this).val() == "Disabled") {
|
||||
istcpEnabled = "Disabled";
|
||||
$(tcpHost).attr("disabled", "disabled");
|
||||
$(tcpPort).attr("disabled", "disabled");
|
||||
} else {
|
||||
istcpEnabled = "Enabled";
|
||||
$(tcpHost).removeAttr("disabled");
|
||||
$(tcpPort).removeAttr("disabled");
|
||||
}
|
||||
}).trigger("change");
|
||||
|
||||
|
||||
AddDropDown(tcpStatus, tcpStatusArray, istcpEnabled);
|
||||
|
||||
// init state of TCP fields
|
||||
if (istcpEnabled === "Disabled")
|
||||
{
|
||||
$(tcpHost).attr("disabled", "disabled");
|
||||
$(tcpPort).attr("disabled", "disabled");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch(e){
|
||||
FailureReadFile(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function AddPathToDeviceFile(incomingArray){
|
||||
for(let t = 0; t < incomingArray.length; t++){
|
||||
incomingArray[t] = "/dev/" + incomingArray[t];
|
||||
}
|
||||
return incomingArray;
|
||||
}
|
||||
|
||||
function AddDropDown(box, theArray, defaultValue){
|
||||
try{
|
||||
for(let t = 0; t < theArray.length; t++){
|
||||
var option = document.createElement("option");
|
||||
option.text = theArray[t];
|
||||
box.add(option);
|
||||
if(defaultValue == option.text){
|
||||
box.value = option.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
Fail(e)
|
||||
}
|
||||
}
|
||||
|
||||
function FailureReadFile(error) {
|
||||
// Display error message
|
||||
console.log("Error : " + error.message);
|
||||
output.innerHTML = "Error : " + error.message;
|
||||
// TODO :: Defaults should go here.
|
||||
losHost.value = "224.10.10.10";
|
||||
losPort.value = "14550";
|
||||
fmuId.value = "1";
|
||||
atakHost.value = "239.2.3.1";
|
||||
atakPort.value = "6969";
|
||||
atakPeriod.value = "5";
|
||||
}
|
||||
|
||||
|
||||
function SaveSettings() {
|
||||
|
||||
//lets do some validation
|
||||
|
||||
var ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
var portformat = /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
|
||||
var errorFlag = false;
|
||||
var errorText = "";
|
||||
if (!losHost.value.match(ipformat) && (isUdpEnabled==="Enabled")) {
|
||||
losHost.focus();
|
||||
errorText += "Error in the UDP Host Address!<br>";
|
||||
errorFlag = true;
|
||||
}
|
||||
if (!losPort.value.match(portformat) && (isUdpEnabled==="Enabled")) {
|
||||
losPort.focus();
|
||||
errorText += "Error in the UDP Port Number! (0-65535 allowed)<br>";
|
||||
errorFlag = true;
|
||||
}
|
||||
if (!tcpHost.value.match(ipformat) && (istcpEnabled==="Enabled")) {
|
||||
tcpHost.focus();
|
||||
errorText += "Error in the TCP Host Address, should be x.x.x.x<br>";
|
||||
errorFlag = true;
|
||||
}
|
||||
if (!tcpPort.value.match(portformat) && (istcpEnabled==="Enabled")) {
|
||||
tcpPort.focus();
|
||||
errorText += "Error in the TCP Port Number! (0-65535 allowed)<br>";
|
||||
errorFlag = true;
|
||||
}
|
||||
|
||||
if (errorFlag)
|
||||
{
|
||||
result.style.color = "red";
|
||||
result.innerHTML = errorText;
|
||||
return;
|
||||
}
|
||||
|
||||
//open the file for writing, and callback function for modification
|
||||
|
||||
cockpit.file(confLocation + "main.conf")
|
||||
.read().then((content, tag) => SuccessReadforSaveFile(content))
|
||||
.catch(error => FailureReadFile(error));
|
||||
|
||||
}
|
||||
|
||||
function SuccessReadforSaveFile(content) {
|
||||
try{
|
||||
|
||||
//if udp is disabled, then skip those
|
||||
if (isUdpEnabled == "Disabled")
|
||||
{
|
||||
content = setSectionEnabled(content, "[UdpEndpoint alpha]", 3, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = setSectionEnabled(content, "[UdpEndpoint alpha]", 3, true);
|
||||
content = setValueByKey(content, "[UdpEndpoint alpha]",3, "Address", losHost.value )
|
||||
content = setValueByKey(content, "[UdpEndpoint alpha]",3, "Port", losPort.value )
|
||||
content = setValueByKey(content, "[UdpEndpoint alpha]",3, "Mode", udpMode.value )
|
||||
}
|
||||
|
||||
if (istcpEnabled == "Disabled")
|
||||
{
|
||||
content = setSectionEnabled(content, "[TcpEndpoint alpha]", 2, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = setSectionEnabled(content, "[TcpEndpoint alpha]", 2, true);
|
||||
content = setValueByKey(content, "[TcpEndpoint alpha]",2, "Address", tcpHost.value )
|
||||
content = setValueByKey(content, "[TcpEndpoint alpha]",2, "Port", tcpPort.value )
|
||||
}
|
||||
|
||||
//at this point we have the contents of the file, we need to replace keys
|
||||
content = setValueByKey(content, "[UartEndpoint alpha]",2, "Device", fmuDevice.value )
|
||||
content = setValueByKey(content, "[UartEndpoint alpha]",2, "Baud", baudrate.value )
|
||||
|
||||
|
||||
|
||||
cockpit.file(confLocation + "main.conf", { superuser : "try" }).replace(content)
|
||||
.then(Success)
|
||||
.catch(Fail);
|
||||
|
||||
cockpit.spawn(["systemctl", "restart", "mavlink-router"], { superuser : "try" });
|
||||
}
|
||||
catch(e){
|
||||
FailureReadFile(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function Success() {
|
||||
result.style.color = "green";
|
||||
result.innerHTML = "Success, mavlink-router restarting...";
|
||||
setTimeout(() => result.innerHTML = "", 5000);
|
||||
}
|
||||
|
||||
function Fail(error) {
|
||||
result.style.color = "red";
|
||||
result.innerHTML = error.message;
|
||||
}
|
||||
// Send a 'init' message. This tells integration tests that we are ready to go
|
||||
cockpit.transport.wait(function() { });
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="#06c" height="36" viewBox="0 -960 960 960" width="36"><path d="M120-80v-280h120v-160h200v-80H320v-280h320v280H520v80h200v160h120v280H520v-280h120v-80H320v80h120v280H120Zm280-600h160v-120H400v120ZM200-160h160v-120H200v120Zm400 0h160v-120H600v120ZM480-680ZM360-280Zm240 0Z"/></svg>
|
After Width: | Height: | Size: 325 B |
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": 0,
|
||||
|
||||
"menu": {
|
||||
"general": {
|
||||
"label": "MAVLink Telemtry",
|
||||
"path": "general.html",
|
||||
"order": 1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="#06c" height="40" viewBox="0 -960 960 960" width="40"><path d="m680-240-56-56 182-184-182-184 56-56 240 240-240 240Zm-400 0L40-480l240-240 56 56-182 184 182 184-56 56Zm40-200q-17 0-28.5-11.5T280-480q0-17 11.5-28.5T320-520q17 0 28.5 11.5T360-480q0 17-11.5 28.5T320-440Zm160 0q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm160 0q-17 0-28.5-11.5T600-480q0-17 11.5-28.5T640-520q17 0 28.5 11.5T680-480q0 17-11.5 28.5T640-440Z"/></svg>
|
After Width: | Height: | Size: 523 B |
|
@ -0,0 +1,805 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Loading...</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex">
|
||||
<meta insert_dynamic_content_here>
|
||||
<script>
|
||||
(function(e) {
|
||||
var t;
|
||||
try {
|
||||
t = window.localStorage;
|
||||
window.localStorage.removeItem("url-root");
|
||||
} catch (n) {
|
||||
t = window.sessionStorage;
|
||||
e.warn(String(n));
|
||||
}
|
||||
var o;
|
||||
var i = window.environment || {};
|
||||
var r = i.OAuth || null;
|
||||
if (r) {
|
||||
if (!r.TokenParam) r.TokenParam = "access_token";
|
||||
if (!r.ErrorParam) r.ErrorParam = "error_description";
|
||||
}
|
||||
var a = /\$\{([^}]+)\}|\$([a-zA-Z0-9_]+)/g;
|
||||
function s(e) {
|
||||
var t = Array.prototype.slice.call(arguments, 1);
|
||||
return e.replace(a, function(e, n, o) {
|
||||
return t[n || o] || "";
|
||||
});
|
||||
}
|
||||
function l(e) {
|
||||
if (window.cockpit_po) {
|
||||
var t = window.cockpit_po[e];
|
||||
if (t && t[1]) return t[1];
|
||||
}
|
||||
return e;
|
||||
}
|
||||
function u() {
|
||||
if (!document.querySelectorAll) return;
|
||||
var e = document.querySelectorAll("[translate]");
|
||||
for (var t = 0; t < e.length; t++) e[t].textContent = l(e[t].textContent);
|
||||
}
|
||||
var c = l;
|
||||
var d, f, p, w;
|
||||
var v = /[?&]?([^=]+)=([^&]*)/g;
|
||||
var g = null;
|
||||
function m(e) {
|
||||
e = e.split("+").join(" ");
|
||||
var t = {};
|
||||
var n;
|
||||
for (;;) {
|
||||
n = v.exec(e);
|
||||
if (!n) break;
|
||||
t[decodeURIComponent(n[1])] = decodeURIComponent(n[2]);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
function y(e) {
|
||||
e = e.trim();
|
||||
if (e[0] == '"') e = e.substr(1, e.length - 2);
|
||||
return e;
|
||||
}
|
||||
if (!e) e = function() {};
|
||||
if (window.location.pathname.indexOf("/cockpit/") === 0 || window.location.pathname.indexOf("/cockpit+") === 0) document.documentElement.setAttribute("class", "inline");
|
||||
function h(e) {
|
||||
return document.getElementById(e);
|
||||
}
|
||||
function b(t) {
|
||||
if (window.console) e.warn("fatal:", t);
|
||||
h("login-again").style.display = "none";
|
||||
h("login-wait-validating").style.display = "none";
|
||||
if (g) {
|
||||
h("login-again").href = g;
|
||||
h("login-again").style.display = "block";
|
||||
}
|
||||
h("login").style.display = "none";
|
||||
h("login-details").style.display = "none";
|
||||
h("login-fatal").style.display = "block";
|
||||
var n = h("login-fatal-message");
|
||||
n.textContent = "";
|
||||
n.appendChild(document.createTextNode(t));
|
||||
}
|
||||
function x(e, t) {
|
||||
var n, o = h(e);
|
||||
if (o) n = window.getComputedStyle(o, ":before");
|
||||
if (!n) return;
|
||||
var i, r = n.content;
|
||||
if (r && r != "none" && r != "normal") {
|
||||
i = r.length;
|
||||
if ((r[0] === '"' || r[0] === "'") && i > 2 && r[i - 1] === r[0]) r = r.substr(1, i - 2);
|
||||
o.innerHTML = r || t;
|
||||
} else {
|
||||
o.removeAttribute("class");
|
||||
}
|
||||
}
|
||||
function k() {
|
||||
function e(e, t) {
|
||||
var n;
|
||||
try {
|
||||
n = t[e];
|
||||
} catch (o) {
|
||||
b(s(c("The web browser configuration prevents Cockpit from running (inaccessible $0)"), e));
|
||||
throw o;
|
||||
}
|
||||
if (n === undefined) {
|
||||
b(s(c("This web browser is too old to run Cockpit (missing $0)"), e));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return ("MozWebSocket" in window || e("WebSocket", window)) && e("XMLHttpRequest", window) && e("sessionStorage", window) && e("JSON", window) && e("defineProperty", Object) && e("console", window) && e("pushState", window.history) && e("textContent", document);
|
||||
}
|
||||
function T(e) {
|
||||
return e.replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
function S(e) {
|
||||
var n = document.createElement("a");
|
||||
var r = document.baseURI;
|
||||
var a;
|
||||
if (!r) {
|
||||
a = document.getElementsByTagName("base");
|
||||
if (a.length > 0) r = a[0].href; else r = "/";
|
||||
}
|
||||
e = e || "/";
|
||||
n.href = r;
|
||||
if (n.pathname != "/") {
|
||||
o = n.pathname.replace(/^\/+|\/+$/g, "");
|
||||
t.setItem("url-root", o);
|
||||
if (o && e.indexOf("/" + o) === 0) e = e.replace("/" + o, "") || "/";
|
||||
}
|
||||
if (e.indexOf("/=") === 0) {
|
||||
i.hostname = e.substring(2);
|
||||
e = "/cockpit+" + e.split("/")[1];
|
||||
} else if (e.indexOf("/cockpit/") !== 0 && e.indexOf("/cockpit+") !== 0) {
|
||||
e = "/cockpit";
|
||||
}
|
||||
f = e.split("/")[1];
|
||||
d = "/" + f + "/login";
|
||||
if (o) d = "/" + o + d;
|
||||
w = f;
|
||||
p = d;
|
||||
}
|
||||
function O(e, t) {
|
||||
if (t === undefined) t = h("server-group").style.display === "none";
|
||||
h("option-group").setAttribute("data-state", t);
|
||||
if (t) {
|
||||
h("server-group").style.display = "block";
|
||||
h("option-caret").setAttribute("class", "caret caret-down");
|
||||
h("option-caret").setAttribute("className", "caret caret-down");
|
||||
} else {
|
||||
h("server-group").style.display = "none";
|
||||
h("option-caret").setAttribute("class", "caret caret-right");
|
||||
h("option-caret").setAttribute("className", "caret caret-right");
|
||||
}
|
||||
}
|
||||
function C() {
|
||||
window.onload = null;
|
||||
u();
|
||||
S(window.location.pathname);
|
||||
var e = i.page.title;
|
||||
if (!e || f.indexOf("cockpit+=") === 0) e = i.hostname;
|
||||
document.title = e;
|
||||
if (f.indexOf("cockpit+=") === 0) {
|
||||
h("brand").style.display = "none";
|
||||
h("badge").style.visibility = "hidden";
|
||||
} else {
|
||||
x("badge", "");
|
||||
x("brand", "Cockpit");
|
||||
}
|
||||
h("option-group").addEventListener("click", O);
|
||||
h("server-clear").addEventListener("click", function() {
|
||||
var e = h("server-field");
|
||||
e.value = "";
|
||||
e.focus();
|
||||
});
|
||||
if (!k()) return;
|
||||
var n = t.getItem("authorized-default") || "";
|
||||
if (n.indexOf("password") !== -1) h("authorized-input").checked = true;
|
||||
var o = i["os-release"];
|
||||
if (o) t.setItem("os-release", JSON.stringify(o));
|
||||
var a = window.sessionStorage.getItem("logout-intent") == "explicit";
|
||||
if (a) window.sessionStorage.removeItem("logout-intent");
|
||||
if (r) {
|
||||
h("login-details").style.display = "none";
|
||||
h("login").style.display = "none";
|
||||
if (a) {
|
||||
E();
|
||||
h("login-again").textContent = c("Login Again");
|
||||
b(c("Logout Successful"));
|
||||
} else {
|
||||
L();
|
||||
}
|
||||
} else if (a) {
|
||||
q();
|
||||
} else {
|
||||
I();
|
||||
}
|
||||
}
|
||||
function I() {
|
||||
var e = new XMLHttpRequest();
|
||||
e.open("GET", d, true);
|
||||
e.onreadystatechange = function() {
|
||||
if (e.readyState != 4) {
|
||||
return;
|
||||
} else if (e.status == 200) {
|
||||
F(JSON.parse(e.responseText));
|
||||
} else if (e.status == 401) {
|
||||
q();
|
||||
} else if (e.statusText) {
|
||||
b(decodeURIComponent(e.statusText));
|
||||
} else if (e.status === 0) {
|
||||
q();
|
||||
} else {
|
||||
b(s(c("$0 error"), e.status));
|
||||
}
|
||||
};
|
||||
e.send();
|
||||
}
|
||||
function E() {
|
||||
var e = window.location.href.split("#", 2);
|
||||
g = r.URL;
|
||||
if (r.URL.indexOf("?") > -1) g += "&"; else g += "?";
|
||||
g += "redirect_uri=" + encodeURIComponent(e[0]);
|
||||
}
|
||||
function L() {
|
||||
var e = document.createElement("a");
|
||||
if (!r.URL) return b(c("Cockpit authentication is configured incorrectly."));
|
||||
var t = m(window.location.search);
|
||||
if (!window.location.search && window.location.hash) t = m(window.location.hash.slice(1));
|
||||
var n, o, i;
|
||||
E();
|
||||
if (t[r.TokenParam]) {
|
||||
if (window.sessionStorage.getItem("login-wanted")) {
|
||||
e.href = window.sessionStorage.getItem("login-wanted");
|
||||
S(e.pathname);
|
||||
}
|
||||
n = t[r.TokenParam];
|
||||
h("login-wait-validating").style.display = "block";
|
||||
i = new XMLHttpRequest();
|
||||
i.open("GET", d, true);
|
||||
i.setRequestHeader("Authorization", "Bearer " + n);
|
||||
i.onreadystatechange = function() {
|
||||
if (i.readyState != 4) {
|
||||
return;
|
||||
} else if (i.status == 200) {
|
||||
F(JSON.parse(i.responseText));
|
||||
} else {
|
||||
o = $(i.getResponseHeader("WWW-Authenticate"), i.responseText);
|
||||
if (o) J(o); else b(i.statusText);
|
||||
}
|
||||
};
|
||||
i.send();
|
||||
} else if (t[r.ErrorParam]) {
|
||||
b(t[r.ErrorParam]);
|
||||
} else {
|
||||
window.sessionStorage.setItem("login-wanted", window.location.href);
|
||||
window.location = g;
|
||||
}
|
||||
}
|
||||
function A() {
|
||||
h("error-group").style.display = "none";
|
||||
h("login-error-message").textContent = "";
|
||||
}
|
||||
function R(e, t) {
|
||||
A();
|
||||
if (e) {
|
||||
if (r) {
|
||||
b(e);
|
||||
} else {
|
||||
P(t);
|
||||
h("login-error-message").textContent = e;
|
||||
h("error-group").style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
function H(e) {
|
||||
var t = h("server-field").value;
|
||||
if (!t) {
|
||||
R(e, false);
|
||||
} else {
|
||||
A();
|
||||
h("login-error-message").textContent = e;
|
||||
h("error-group").style.display = "block";
|
||||
O(null, true);
|
||||
P();
|
||||
}
|
||||
}
|
||||
function N(e) {
|
||||
var t = h("login-note");
|
||||
if (e) {
|
||||
t.style.display = "block";
|
||||
t.textContent = e;
|
||||
} else {
|
||||
t.innerHTML = " ";
|
||||
}
|
||||
}
|
||||
function U() {
|
||||
return i.page.require_host && w.indexOf("cockpit+=") === -1;
|
||||
}
|
||||
function z() {
|
||||
R(null);
|
||||
var e, n = T(h("login-user-input").value);
|
||||
if (n === "") {
|
||||
R(c("User name cannot be empty"));
|
||||
} else if (U() && h("server-field").value === "") {
|
||||
R(c("Please specify the host to connect to"));
|
||||
} else {
|
||||
e = h("server-field").value;
|
||||
if (e) {
|
||||
f = "cockpit+=" + e;
|
||||
d = p.replace("/" + w + "/", "/" + f + "/");
|
||||
} else {
|
||||
f = w;
|
||||
d = p;
|
||||
}
|
||||
h("server-name").textContent = e || i.hostname;
|
||||
h("login-button").removeEventListener("click", z);
|
||||
var o = h("authorized-input").checked ? "password" : "";
|
||||
var r = h("login-password-input").value;
|
||||
t.setItem("authorized-default", o);
|
||||
var a = {
|
||||
Authorization: "Basic " + window.btoa(W(n + ":" + r)),
|
||||
"X-Authorize": o
|
||||
};
|
||||
M("GET", a, false);
|
||||
}
|
||||
}
|
||||
function P(e) {
|
||||
var t = i.page.connect;
|
||||
var n = h("option-group").getAttribute("data-state");
|
||||
h("login-wait-validating").style.display = "none";
|
||||
h("login").style.visibility = "visible";
|
||||
h("login").style.display = "block";
|
||||
h("user-group").style.display = e ? "none" : "block";
|
||||
h("password-group").style.display = e ? "none" : "block";
|
||||
h("conversation-group").style.display = e ? "block" : "none";
|
||||
h("login-button-text").textContent = c("Log In");
|
||||
h("login-password-input").value = "";
|
||||
if (U()) {
|
||||
h("option-group").style.display = "none";
|
||||
n = true;
|
||||
} else {
|
||||
h("option-group").style.display = !t || e ? "none" : "block";
|
||||
}
|
||||
if (!t || e) {
|
||||
h("server-group").style.display = "none";
|
||||
} else {
|
||||
h("server-group").style.display = n ? "block" : "none";
|
||||
}
|
||||
h("login-button").removeAttribute("disabled");
|
||||
if (!e) h("login-button").addEventListener("click", z);
|
||||
}
|
||||
function q() {
|
||||
h("server-name").textContent = document.title;
|
||||
N(c("Log in with your server user account."));
|
||||
h("login-user-input").addEventListener("keydown", function(e) {
|
||||
R(null);
|
||||
if (e.which == 13) h("login-password-input").focus();
|
||||
}, false);
|
||||
var e = function(e) {
|
||||
R(null);
|
||||
if (e.which == 13) z();
|
||||
};
|
||||
h("login-password-input").addEventListener("keydown", e);
|
||||
h("authorized-input").addEventListener("keydown", e);
|
||||
P();
|
||||
h("login-user-input").focus();
|
||||
}
|
||||
function J(e) {
|
||||
var t = e.echo ? "text" : "password";
|
||||
h("conversation-prompt").textContent = e.prompt;
|
||||
var n = h("conversation-message");
|
||||
var o = e.error || e.message;
|
||||
if (o) {
|
||||
n.textContent = o;
|
||||
n.style.display = "block";
|
||||
} else {
|
||||
n.style.display = "none";
|
||||
}
|
||||
var i = h("conversation-input");
|
||||
i.value = "";
|
||||
if (e.default) i.value = e.default;
|
||||
i.setAttribute("type", t);
|
||||
i.focus();
|
||||
R("");
|
||||
function r() {
|
||||
h("conversation-input").removeEventListener("keydown", a);
|
||||
h("login-button").removeEventListener("click", r);
|
||||
R(null, true);
|
||||
G(e.id, h("conversation-input").value);
|
||||
}
|
||||
function a(e) {
|
||||
R(null, true);
|
||||
if (e.which == 13) {
|
||||
r();
|
||||
}
|
||||
}
|
||||
h("conversation-input").addEventListener("keydown", a);
|
||||
h("login-button").addEventListener("click", r);
|
||||
P(true);
|
||||
}
|
||||
function W(e) {
|
||||
return window.unescape(encodeURIComponent(e));
|
||||
}
|
||||
function $(t, n) {
|
||||
var o;
|
||||
var i;
|
||||
var r;
|
||||
var a;
|
||||
if (!t) return null;
|
||||
o = t.split(" ");
|
||||
if (o[0].toLowerCase() !== "x-conversation" && o.length != 3) return null;
|
||||
a = o[1];
|
||||
try {
|
||||
i = window.atob(o[2]);
|
||||
} catch (s) {
|
||||
if (window.console) e.error("Invalid prompt data", s);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
r = JSON.parse(n);
|
||||
} catch (s) {
|
||||
if (window.console) e.log("Got invalid JSON response for prompt data", s);
|
||||
r = {};
|
||||
}
|
||||
r.id = a;
|
||||
r.prompt = i;
|
||||
return r;
|
||||
}
|
||||
function M(t, n, o) {
|
||||
h("login-button").setAttribute("disabled", "true");
|
||||
var i = new XMLHttpRequest();
|
||||
i.open("GET", d, true);
|
||||
var r;
|
||||
var a;
|
||||
var l;
|
||||
for (l in n) i.setRequestHeader(l, n[l]);
|
||||
i.onreadystatechange = function() {
|
||||
if (i.readyState != 4) {
|
||||
return;
|
||||
} else if (i.status == 200) {
|
||||
var t = JSON.parse(i.responseText);
|
||||
F(t);
|
||||
} else if (i.status == 401) {
|
||||
a = i.getResponseHeader("WWW-Authenticate");
|
||||
if (a && a.toLowerCase().indexOf("x-conversation") === 0) {
|
||||
r = $(a, i.responseText);
|
||||
if (r) J(r); else b(c("Internal Error: Invalid challenge header"));
|
||||
} else {
|
||||
if (window.console) e.log(i.statusText);
|
||||
if (i.statusText.indexOf("authentication-not-supported") > -1) {
|
||||
var n = T(h("login-user-input").value);
|
||||
b(s(c("The server refused to authenticate '$0' using password authentication, and no other supported authentication methods are available."), n));
|
||||
} else if (i.statusText.indexOf("terminated") > -1) {
|
||||
R(c("Authentication Failed: Server closed connection"));
|
||||
} else if (i.statusText.indexOf("no-host") > -1) {
|
||||
H(c("Unable to connect to that address"));
|
||||
} else if (i.statusText.indexOf("unknown-hostkey") > -1) {
|
||||
H(c("Refusing to connect. Hostkey is unknown"));
|
||||
} else if (i.statusText.indexOf("unknown-host") > -1) {
|
||||
H(c("Refusing to connect. Host is unknown"));
|
||||
} else if (i.statusText.indexOf("invalid-hostkey") > -1) {
|
||||
H(c("Refusing to connect. Hostkey does not match"));
|
||||
} else if (o) {
|
||||
R(c("Authentication failed"));
|
||||
} else {
|
||||
R(c("Wrong user name or password"));
|
||||
}
|
||||
}
|
||||
} else if (i.status == 403) {
|
||||
R(decodeURIComponent(i.statusText) || c("Permission denied"));
|
||||
} else if (i.statusText) {
|
||||
b(decodeURIComponent(i.statusText));
|
||||
} else {
|
||||
b(s(c("$0 error"), i.status));
|
||||
}
|
||||
h("login-button").removeAttribute("disabled");
|
||||
};
|
||||
i.send();
|
||||
}
|
||||
function G(e, t) {
|
||||
var n = {
|
||||
Authorization: "X-Conversation " + e + " " + window.btoa(W(t))
|
||||
};
|
||||
M("GET", n, true);
|
||||
}
|
||||
function X(e) {
|
||||
var t = window.setTimeout(function() {
|
||||
t = null;
|
||||
window.location.reload(true);
|
||||
}, 100);
|
||||
if (e && e != window.location.href) window.location = e;
|
||||
window.onbeforeunload = function() {
|
||||
if (t) window.clearTimeout(t);
|
||||
t = null;
|
||||
};
|
||||
}
|
||||
function _(e) {
|
||||
var t = "/" + f + "/@localhost/";
|
||||
if (o) t = "/" + o + t;
|
||||
var n = t + "shell/index.html";
|
||||
var i = new XMLHttpRequest();
|
||||
i.open("GET", t + "manifests.json", true);
|
||||
i.onreadystatechange = function() {
|
||||
if (i.readyState != 4) {
|
||||
return;
|
||||
} else if (i.status == 200) {
|
||||
var t = JSON.parse(i.responseText);
|
||||
var o = t ? t["base1"] : {};
|
||||
if (!o["version"] || o["version"] < "119.x") {
|
||||
X(n);
|
||||
} else X(e);
|
||||
} else {
|
||||
X(n);
|
||||
}
|
||||
};
|
||||
i.send();
|
||||
}
|
||||
function B(e, t, n) {
|
||||
var o = 0;
|
||||
while (o < e.length) {
|
||||
var i = e.key(o);
|
||||
if (n && i.indexOf("cockpit") !== 0) e.removeItem(i); else if (i.indexOf(t) === 0) e.removeItem(i); else o++;
|
||||
}
|
||||
}
|
||||
function j(e) {
|
||||
B(window.sessionStorage, f, true);
|
||||
t.removeItem("login-data");
|
||||
B(t, f, false);
|
||||
var n;
|
||||
if (e && e["login-data"]) {
|
||||
n = JSON.stringify(e["login-data"]);
|
||||
t.setItem(f + "login-data", n);
|
||||
t.setItem("login-data", n);
|
||||
}
|
||||
if (o) t.setItem("url-root", o);
|
||||
}
|
||||
function F(e) {
|
||||
var t = window.sessionStorage.getItem("login-wanted");
|
||||
var n = h("server-field").value;
|
||||
var i;
|
||||
if (n && f != w) {
|
||||
t = "/=" + n;
|
||||
if (o) t = "/" + o + t;
|
||||
}
|
||||
B(window.sessionStorage, f, false);
|
||||
j(e);
|
||||
if (f.indexOf("cockpit+=") === 0) {
|
||||
_(t);
|
||||
} else {
|
||||
X(t);
|
||||
}
|
||||
}
|
||||
window.onload = C;
|
||||
})(window.console);
|
||||
//# sourceMappingURL=login.min.js.map
|
||||
</script>
|
||||
<style>
|
||||
#option-group,.btn,.cross,button{cursor:pointer}
|
||||
.btn,label{font-weight:600}
|
||||
.btn,img{vertical-align:middle}
|
||||
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-size:62.5%;-webkit-tap-highlight-color:transparent}
|
||||
body{margin:0;font-family:"Open Sans",Helvetica,Arial,sans-serif;font-size:12px;line-height:1.66666667;background-color:#fff}
|
||||
a{background:0 0;color:#0099d3;text-decoration:none}
|
||||
.btn,.btn:active{background-image:none}
|
||||
a:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}
|
||||
a:active,a:hover{outline:0}
|
||||
a:focus,a:hover{color:#00618a;text-decoration:underline}
|
||||
img{border:0}
|
||||
button,input,select,textarea{font-family:inherit;margin:0;font-size:inherit;line-height:inherit}
|
||||
button,input{line-height:normal}
|
||||
.btn,.form-control{line-height:1.66666667}
|
||||
button,select{text-transform:none}
|
||||
button{-webkit-appearance:button;overflow:visible}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
|
||||
*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
|
||||
p{margin:0 0 10px}
|
||||
.container{margin-right:auto;margin-left:auto;padding-left:20px;padding-right:20px}
|
||||
.form-group,.row{margin-right:-20px}
|
||||
.container:after,.container:before,.row:after,.row:before{content:" ";display:table}
|
||||
.container:after,.row:after{clear:both}
|
||||
@media (min-width:768px){.container{width:760px}
|
||||
}
|
||||
@media (min-width:992px){.container{width:980px}
|
||||
}
|
||||
@media (min-width:1200px){.container{width:1180px}
|
||||
}
|
||||
.row{margin-left:-20px}
|
||||
.col-lg-5,.col-lg-7,.col-md-10,.col-md-2,.col-md-6,.col-sm-1,.col-sm-10,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-5,.col-sm-6,.col-sm-7,.col-xs-12{position:relative;min-height:1px;padding-left:20px;padding-right:20px}
|
||||
.col-xs-12{float:left;width:100%}
|
||||
@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-5,.col-sm-6,.col-sm-7{float:left}
|
||||
.col-sm-12{width:100%}
|
||||
.col-sm-10{width:83.33333333333334%}
|
||||
.col-sm-7{width:58.333333333333336%}
|
||||
.col-sm-6{width:50%}
|
||||
.col-sm-5{width:41.66666666666667%}
|
||||
.col-sm-3{width:25%}
|
||||
.col-sm-2{width:16.666666666666664%}
|
||||
.col-sm-1{width:8.333333333333332%}
|
||||
.col-sm-offset-2{margin-left:16.666666666666664%}
|
||||
.control-label{text-align:right}
|
||||
}
|
||||
@media (min-width:992px){.col-md-10,.col-md-2,.col-md-6{float:left}
|
||||
.col-md-10{width:83.33333333333334%}
|
||||
.col-md-6{width:50%}
|
||||
.col-md-2{width:16.666666666666664%}
|
||||
}
|
||||
@media (min-width:1200px){.col-lg-5,.col-lg-7{float:left}
|
||||
.col-lg-7{width:58.333333333333336%}
|
||||
.col-lg-5{width:41.66666666666667%}
|
||||
}
|
||||
label{display:inline-block;margin-bottom:5px}
|
||||
.form-control{height:26px;color:#333}
|
||||
.form-control[type=text],.form-control[type=password]{display:block;width:100%;padding:2px 6px;font-size:12px;background-color:#fff;background-image:none;border:1px solid #bababa;border-radius:1px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}
|
||||
.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}
|
||||
.form-control:-moz-placeholder{color:#999;font-style:italic}
|
||||
.form-control::-moz-placeholder{color:#999;font-style:italic;opacity:1}
|
||||
.form-control:-ms-input-placeholder{color:#999;font-style:italic}
|
||||
.form-control::-webkit-input-placeholder{color:#999;font-style:italic}
|
||||
.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}
|
||||
.control-label{margin-top:0;margin-bottom:0;padding-top:3px}
|
||||
.form-group{margin-left:-20px}
|
||||
.form-group:after{clear:both;margin-bottom:15px}
|
||||
.form-group:after,.form-group:before{content:" ";display:table}
|
||||
.btn{display:inline-block;margin-bottom:0;text-align:center;border:1px solid transparent;white-space:nowrap;padding:2px 6px;font-size:12px;border-radius:1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;-webkit-box-shadow:0 2px 3px rgba(0,0,0,.1);box-shadow:0 2px 3px rgba(0,0,0,.1)}
|
||||
.btn:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}
|
||||
.btn:focus,.btn:hover{color:#4d5258;text-decoration:none}
|
||||
.alert-danger,.btn-primary,.login-pf .container .help-block,body{color:#fff}
|
||||
.btn:active{outline:0;-webkit-box-shadow:inset 0 2px 8px rgba(0,0,0,.2);box-shadow:inset 0 2px 8px rgba(0,0,0,.2)}
|
||||
.btn-lg{padding:6px 10px;font-size:14px;line-height:1.33;border-radius:1px}
|
||||
@-ms-viewport{width:device-width}
|
||||
.btn-primary{background-color:#189ad1;background-image:-webkit-linear-gradient(top,#1cace8 0,#1998cc 100%);background-image:linear-gradient(to bottom,#1cace8 0,#1998cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1cace8', endColorstr='#ff1998cc', GradientType=0);border-color:#267da1}
|
||||
.btn-primary:active,.btn-primary:focus,.btn-primary:hover{background-color:#189ad1;background-image:none;border-color:#267da1;color:#fff}
|
||||
.btn-primary:active{background-image:none}
|
||||
@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;src:url(cockpit/static/fonts/OpenSans-Regular-webfont.woff) format('woff')}
|
||||
@font-face{font-family:'Open Sans';font-style:normal;font-weight:700;src:url(cockpit/static/fonts/OpenSans-Bold-webfont.woff) format('woff')}
|
||||
.form-control:hover{border-color:#7BB2DD}
|
||||
.login-pf{height:100%}
|
||||
.login-pf #brand{position:relative;top:-70px}
|
||||
.login-pf #brand img{display:block;margin:0 auto;max-width:100%}
|
||||
@media (min-width:768px){.login-pf #brand img{margin:0;text-align:left}
|
||||
}
|
||||
.login-pf #badge{display:block;margin:20px auto 70px;position:relative;text-align:center}
|
||||
.login-pf .container{background-color:#181818;background-color:rgba(255,255,255,.055);clear:right;padding-bottom:40px;padding-top:20px;width:auto}
|
||||
@media (min-width:768px){.login-pf #badge{float:right;margin-right:64px;margin-top:50px}
|
||||
.login-pf .container{bottom:13%;padding-left:80px;position:absolute;width:100%}
|
||||
}
|
||||
.caret,.server-box,.spinner{position:relative}
|
||||
.login-pf .container .details p:first-child{border-top:1px solid #474747;padding-top:25px;margin-top:25px}
|
||||
@media (min-width:768px){.login-pf .container .login-area{border-right:0px solid #474747}
|
||||
.login-pf .container .details{padding-left:40px}
|
||||
.login-pf .container .details p:first-child{border-top:0;padding-top:0;margin-top:0}
|
||||
}
|
||||
.login-pf .container .details p{margin-bottom:2px}
|
||||
.login-pf .container .control-label{font-size:13px;font-weight:400;text-align:left}
|
||||
.login-pf .container .form-group:last-child,.login-pf .container .form-group:last-child .help-block:last-child{margin-bottom:0}
|
||||
@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}
|
||||
to{-webkit-transform:rotate(359deg)}
|
||||
}
|
||||
@keyframes rotation{from{transform:rotate(0)}
|
||||
to{transform:rotate(359deg)}
|
||||
}
|
||||
.spinner{-webkit-animation:rotation .6s infinite linear;animation:rotation .6s infinite linear;border-bottom:4px solid rgba(0,0,0,.25);border-left:4px solid rgba(0,0,0,.25);border-right:4px solid rgba(0,0,0,.25);border-radius:100%;border-top:4px solid rgba(0,0,0,.75);height:24px;margin:4px 0 0;width:24px}
|
||||
.alert{padding:7px 11px;margin-bottom:20px;border:2px solid transparent;border-radius:1px}
|
||||
.alert-danger{background:0 0;border-color:#c00;font-weight:700}
|
||||
#option-group{margin-left:-20px;margin-right:-20px}
|
||||
#server-group:before{clear:both;margin-top:5px}
|
||||
.cross,.inline .container .help-block{color:#000}
|
||||
.login-fatal{font-size:130%}
|
||||
#login-wait-validating div{float:left}
|
||||
.conversation-prompt{white-space:normal;word-wrap:break-word}
|
||||
.control-label{white-space:nowrap;font-size:13px}
|
||||
.spinner{border-color:rgba(255,255,255,.75) rgba(255,255,255,.25) rgba(255,255,255,.25)}
|
||||
.inline #badge,.inline #brand,.inline #login-details{display:none}
|
||||
.inline body{background:0 0!important;color:#000}
|
||||
@media (min-width:768px){.login-button-container{float:right}
|
||||
}
|
||||
.caret{display:inline-block;top:4px}
|
||||
.caret-down{transform:rotate(90deg);-moz-transform:rotate(90deg);-webkit-transform:rotate(90deg);transform-origin:8px 8px;-moz-transform-origin:8px 8px;-webkit-transform-origin:8px 8px}
|
||||
.cross{position:absolute;right:25px;top:2px;font-weight:700;font-size:14px;opacity:.7}
|
||||
#option-group:hover svg,.cross:hover{opacity:1}
|
||||
#option-group div{margin-left:-3px;margin-top:3px;margin-bottom:10px}
|
||||
#option-group svg{opacity:.7}
|
||||
#authorized-input{width:13px;height:13px;padding:0;vertical-align:bottom;margin:8px 5px 3px 0}
|
||||
#login-button{padding:7px}
|
||||
#login-button .spinner{display:none}
|
||||
#login-button[disabled]{padding:0;background-color:#333;background-image:none;border-color:#555}
|
||||
#login-button[disabled] .spinner{display:inline-block}
|
||||
#login-button[disabled] #login-button-text,.hide-before:before{display:none}
|
||||
@media (max-width:480px){.login-pf{display:flex;flex-direction:column-reverse;height:auto;position:relative}
|
||||
.row{display:flex;flex-direction:column}
|
||||
.login-pf .container{width:100%}
|
||||
.login-pf #badge{max-width:33vw;margin:3rem auto;height:8rem;padding:0;background-position:50% 100%}
|
||||
.container>.row>.col-sm-12{order:1;position:absolute;bottom:0;left:0;width:100%;text-align:center}
|
||||
.login-pf #brand{position:static;font-size:inherit;background-position:50% 50%}
|
||||
.details{text-align:center}
|
||||
}
|
||||
/*# sourceMappingURL=login.min.css.map */ </style>
|
||||
<link href="cockpit/static/branding.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="login-pf">
|
||||
<span id="badge">
|
||||
</span>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div id="brand" class="hide-before">
|
||||
</div><!--/#brand-->
|
||||
</div><!--/.col-*-->
|
||||
|
||||
<div id="login" class="col-sm-7 col-md-6 col-lg-5 login-area" style="visibility: hidden;">
|
||||
<div role="form">
|
||||
|
||||
<div id="error-group" class="alert alert-danger" hidden>
|
||||
<span id="login-error-message"></span>
|
||||
</div>
|
||||
|
||||
<div id="conversation-group" class="form-group" hidden>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div id="conversation-message"></div>
|
||||
<label id="conversation-prompt" for="conversation-input"></label>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<input type="password" class="form-control" id="conversation-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="user-group" class="form-group">
|
||||
<label for="login-user-input" class="col-sm-2 col-md-2 control-label" translate>User name</label>
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="text" class="form-control" id="login-user-input" autocorrect="off" autocapitalize="none" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="password-group" class="form-group">
|
||||
<label for="login-password-input" class="col-sm-2 col-md-2 control-label" translate>Password</label>
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="login-password-input">
|
||||
</div>
|
||||
<div class="col-sm-2 col-md-2"></div>
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="checkbox" class="form-control" id="authorized-input" checked style="display:none;">
|
||||
<label for="authorized-input" class="control-label" translate style="display:none;">Reuse my password for privileged tasks</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="option-group" style="display:none;">
|
||||
<div class="col-sm-5 col-md-5" style="display:none;">
|
||||
<i id="option-caret" class="caret caret-right" aria-hidden="true">
|
||||
<svg height="16" width="16" viewBox="0 0 16 16">
|
||||
<polygon fill="#ffffff" points="4,0 4,14 12,7">
|
||||
</polygon>
|
||||
</svg>
|
||||
</i>
|
||||
<span translate>Other Options</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="server-group" class="form-group" hidden style="display:none;">
|
||||
<label title="Log in to another system. Leave blank to log in to the local system." for="server-field" class="col-sm-2 col-md-2 control-label" translate>Connect to</label>
|
||||
<div class="col-sm-10 col-md-10 server-box">
|
||||
<input type="text" class="form-control" id="server-field">
|
||||
<span class="cross" id="server-clear" aria-hidden="true">❌</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-3 col-sm-3 login-button-container">
|
||||
<button class="btn btn-primary btn-lg col-xs-12" id="login-button">
|
||||
<div class="spinner"></div>
|
||||
<div id="login-button-text" translate>Log In</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!--/.col-*-->
|
||||
|
||||
<div class="col-sm-5 col-md-6 col-lg-7 details" id="login-details" style="display:none;">
|
||||
<p>
|
||||
<label class="control-label"><span translate>Server</span>: <b id="server-name"></b></label>
|
||||
</p>
|
||||
<p id="login-note" class="login-note"></p>
|
||||
</div><!--/.col-*-->
|
||||
|
||||
<div class="col-sm-5 col-md-6 col-lg-7" id="login-wait-validating" hidden>
|
||||
<div class="col-sm-4">
|
||||
<span class="help-block" translate>Validating authentication token</span>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<div class="spinner col-xs-15">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12" id="login-fatal" hidden>
|
||||
<span id="login-fatal-message"></span>
|
||||
<a id="login-again" translate hidden>Try Again</a>
|
||||
</div>
|
||||
|
||||
</div><!--/.row-->
|
||||
</div><!--/.container-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
mavlink-router (EchoMAV Fork)
|
Loading…
Reference in New Issue