Move log pages to Vue components.

This commit is contained in:
Buster "Silver Eagle" Neece 2022-06-19 17:28:31 -05:00
parent 7084860515
commit 852d2e4de1
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
27 changed files with 499 additions and 413 deletions

View File

@ -268,7 +268,7 @@ return function (App\Event\BuildStationMenu $e) {
'help' => [
'label' => __('Help'),
'icon' => 'support',
'url' => (string)$router->fromHere('stations:logs:index'),
'url' => (string)$router->fromHere('stations:help'),
'permission' => StationPermissions::Logs,
],
]

View File

@ -91,17 +91,9 @@ return static function (RouteCollectorProxy $app) {
->setName('admin:custom_fields:index')
->add(new Middleware\Permissions(GlobalPermissions::CustomFields));
$group->group(
'/logs',
function (RouteCollectorProxy $group) {
$group->get('', Controller\Admin\LogsController::class)
->setName('admin:logs:index');
$group->get('/view/{station_id}/{log}', Controller\Admin\LogsController::class . ':viewAction')
->setName('admin:logs:view')
->add(Middleware\GetStation::class);
}
)->add(new Middleware\Permissions(GlobalPermissions::Logs));
$group->get('/logs', Controller\Admin\LogsAction::class)
->setName('admin:logs:index')
->add(new Middleware\Permissions(GlobalPermissions::Logs));
$group->get('/permissions', Controller\Admin\PermissionsAction::class)
->setName('admin:permissions:index')

View File

@ -188,6 +188,17 @@ return static function (RouteCollectorProxy $group) {
Controller\Api\Admin\Stations\StorageLocationsAction::class
)->setName('api:admin:stations:storage-locations')
->add(new Middleware\Permissions(GlobalPermissions::Stations));
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->get('/logs', Controller\Api\Admin\LogsAction::class)
->setName('api:admin:logs');
$group->get('/log/{log}', Controller\Api\Admin\LogsAction::class)
->setName('api:admin:log');
}
)->add(new Middleware\Permissions(GlobalPermissions::Logs));
}
);
};

View File

@ -640,6 +640,17 @@ return static function (RouteCollectorProxy $group) {
)->setName('api:stations:webhook:test-log');
}
)->add(new Middleware\Permissions(StationPermissions::WebHooks, true));
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->get('/logs', Controller\Api\Stations\LogsAction::class)
->setName('api:stations:logs');
$group->get('/log/{log}', Controller\Api\Stations\LogsAction::class)
->setName('api:stations:log');
}
)->add(new Middleware\Permissions(StationPermissions::Logs, true));
}
)->add(Middleware\RequireStation::class)
->add(Middleware\GetStation::class);

View File

@ -44,16 +44,9 @@ return static function (RouteCollectorProxy $app) {
->setName('stations:stereo_tool_config')
->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
$group->group(
'/logs',
function (RouteCollectorProxy $group) {
$group->get('', Controller\Stations\LogsController::class)
->setName('stations:logs:index');
$group->get('/view/{log}', Controller\Stations\LogsController::class . ':viewAction')
->setName('stations:logs:view');
}
)->add(new Middleware\Permissions(StationPermissions::Logs, true));
$group->get('/help', Controller\Stations\HelpAction::class)
->setName('stations:help')
->add(new Middleware\Permissions(StationPermissions::Logs, true));
$group->get('/playlists', Controller\Stations\PlaylistsAction::class)
->setName('stations:playlists:index')

View File

@ -0,0 +1,48 @@
<template>
<div>
<div class="card mb-3">
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<translate key="hdr_system_logs">System Logs</translate>
</h2>
</div>
<log-list :url="systemLogsUrl" @view="viewLog"></log-list>
</div>
<div class="card" v-if="stationLogs.length > 0">
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<translate key="hdr_logs_by_station">Logs by Station</translate>
</h2>
</div>
<b-tabs pills lazy nav-class="card-header-pills" nav-wrapper-class="card-header">
<b-tab v-for="row in stationLogs" :key="row.id" :title="row.name">
<log-list :url="row.url" @view="viewLog"></log-list>
</b-tab>
</b-tabs>
</div>
<streaming-log-modal ref="modal"></streaming-log-modal>
</div>
</template>
<script>
import LogList from "~/components/Common/LogList";
import StreamingLogModal from "~/components/Common/StreamingLogModal";
export default {
name: 'AdminLogs',
components: {StreamingLogModal, LogList},
props: {
systemLogsUrl: String,
stationLogs: Array
},
methods: {
viewLog(url) {
this.$refs.modal.show(url);
}
}
}
</script>

View File

@ -0,0 +1,3 @@
<template>
</template>

View File

@ -0,0 +1,43 @@
<template>
<div class="list-group list-group-flush">
<a v-for="log in logs" :key="log.key" class="list-group-item list-group-item-action log-item"
href="#" @click.prevent="viewLog(log.links.self)">
<span class="log-name">{{ log.name }}</span><br>
<small class="text-secondary">{{ log.path }}</small>
</a>
</div>
</template>
<script>
export default {
name: 'LogList',
emits: ['view'],
props: {
url: String,
},
data() {
return {
loading: true,
logs: []
}
},
mounted() {
this.relist();
},
methods: {
relist() {
this.loading = true;
this.$wrapWithLoading(
this.axios.get(this.url)
).then((resp) => {
this.logs = resp.data.logs;
}).finally(() => {
this.loading = false;
});
},
viewLog(url) {
this.$emit('view', url);
}
}
}
</script>

View File

@ -22,7 +22,7 @@ export default {
components: {StreamingLogView},
data() {
return {
logUrl: null,
logUrl: null
};
},
computed: {

View File

@ -1,14 +1,23 @@
<template>
<b-overlay variant="card" :show="loading">
<textarea class="form-control log-viewer" id="log-view-contents" spellcheck="false"
<b-form-group label-for="modal_scroll_to_bottom">
<b-form-checkbox id="modal_scroll_to_bottom" v-model="scrollToBottom">
<translate key="scroll_to_bottom">Automatically Scroll to Bottom</translate>
</b-form-checkbox>
</b-form-group>
<textarea class="form-control log-viewer" ref="textarea" id="log-view-contents" spellcheck="false"
readonly>{{ logs }}</textarea>
</b-overlay>
</template>
<script>
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
export default {
name: 'StreamingLogView',
components: {BWrappedFormCheckbox},
props: {
logUrl: {
type: String,
@ -21,6 +30,7 @@ export default {
logs: '',
currentLogPosition: null,
timeoutUpdateLog: null,
scrollToBottom: true,
};
},
mounted() {
@ -32,6 +42,7 @@ export default {
}).then((resp) => {
if (resp.data.contents !== '') {
this.logs = resp.data.contents + "\n";
this.scrollTextarea();
} else {
this.logs = '';
}
@ -59,6 +70,7 @@ export default {
}).then((resp) => {
if (resp.data.contents !== '') {
this.logs = this.logs + resp.data.contents + "\n";
this.scrollTextarea();
}
this.currentLogPosition = resp.data.position;
@ -70,6 +82,14 @@ export default {
getContents() {
return this.logs;
},
scrollTextarea() {
if (this.scrollToBottom) {
this.$nextTick(() => {
const textarea = this.$refs.textarea;
textarea.scrollTop = textarea.scrollHeight;
});
}
}
}
};
</script>

View File

@ -0,0 +1,65 @@
<template>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<translate key="hdr_logs">Available Logs</translate>
</h2>
</div>
<log-list :url="logsUrl" @view="viewLog"></log-list>
</div>
<streaming-log-modal ref="modal"></streaming-log-modal>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<translate key="hdr_need_help">Need Help?</translate>
</h2>
</div>
<div class="card-body">
<p class="card-text">
<translate key="help_section_1">You can find answers for many common questions in our support documents.</translate>
</p>
<p class="card-text">
<a href="https://docs.azuracast.com/en/user-guide/troubleshooting" target="_blank">
<translate key="help_link_support_docs">Support Documents</translate>
</a>
</p>
<p class="card-text">
<translate key="help_section_2">If you're experiencing a bug or error, you can submit a GitHub issue using the link below.</translate>
</p>
</div>
<div class="card-actions">
<a class="btn btn-outline-primary" role="button"
href="https://github.com/AzuraCast/AzuraCast/issues/new/choose" target="_blank">
<icon icon="contact_support"></icon>
<translate key="btn_add_github_issue">Add New GitHub Issue</translate>
</a>
</div>
</div>
</div>
</div>
</template>
<script>
import Icon from "~/components/Common/Icon";
import StreamingLogModal from "~/components/Common/StreamingLogModal";
import LogList from "~/components/Common/LogList";
export default {
name: 'StationsHelp',
components: {LogList, StreamingLogModal, Icon},
props: {
logsUrl: String,
},
methods: {
viewLog(url) {
this.$refs.modal.show(url);
}
}
}
</script>

View File

@ -0,0 +1,7 @@
import initBase from '~/base.js';
import '~/vendor/bootstrapVue.js';
import AdminLogs from '~/components/Admin/Logs.vue';
export default initBase(AdminLogs);

View File

@ -0,0 +1,7 @@
import initBase from '~/base.js';
import '~/vendor/bootstrapVue.js';
import Help from '~/components/Stations/Help.vue';
export default initBase(Help);

View File

@ -15,6 +15,7 @@ module.exports = {
AdminCustomFields: '~/pages/Admin/CustomFields.js',
AdminGeoLite: '~/pages/Admin/GeoLite.js',
AdminIndex: '~/pages/Admin/Index.js',
AdminLogs: '~/pages/Admin/Logs.js',
AdminPermissions: '~/pages/Admin/Permissions.js',
AdminSettings: '~/pages/Admin/Settings.js',
AdminShoutcast: '~/pages/Admin/Shoutcast.js',
@ -35,6 +36,7 @@ module.exports = {
SetupStation: '~/pages/Setup/Station.js',
StationsBulkMedia: '~/pages/Stations/BulkMedia.js',
StationsFallback: '~/pages/Stations/Fallback.js',
StationsHelp: '~/pages/Stations/Help.js',
StationsHlsStreams: '~/pages/Stations/HlsStreams.js',
StationsLiquidsoapConfig: '~/pages/Stations/LiquidsoapConfig.js',
StationsMedia: '~/pages/Stations/Media.js',

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Repository\StationRepository;
use App\Enums\StationPermissions;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
final class LogsAction
{
public function __construct(
private readonly StationRepository $stationRepo
) {
}
public function __invoke(
ServerRequest $request,
Response $response
): ResponseInterface {
$router = $request->getRouter();
$acl = $request->getAcl();
$stationLogs = [];
foreach ($this->stationRepo->iterateEnabledStations() as $station) {
if ($acl->isAllowed(StationPermissions::Logs, $station)) {
$stationLogs[] = [
'id' => $station->getIdRequired(),
'name' => $station->getName(),
'url' => (string)$router->named('api:stations:logs', [
'station_id' => $station->getIdRequired(),
]),
];
}
}
return $request->getView()->renderVuePage(
response: $response,
component: 'Vue_AdminLogs',
id: 'admin-logs',
title: __('System Logs'),
props: [
'systemLogsUrl' => (string)$router->fromHere('api:admin:logs'),
'stationLogs' => $stationLogs,
],
);
}
}

View File

@ -2,47 +2,64 @@
declare(strict_types=1);
namespace App\Controller\Admin;
namespace App\Controller\Api\Admin;
use App\Controller\AbstractLogViewerController;
use App\Entity;
use App\Controller\Api\Traits\HasLogViewer;
use App\Environment;
use App\Exception;
use App\Http\Response;
use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
final class LogsController extends AbstractLogViewerController
final class LogsAction
{
use HasLogViewer;
public function __construct(
private readonly EntityManagerInterface $em,
private readonly Environment $environment
) {
}
public function __invoke(
ServerRequest $request,
Response $response
Response $response,
?string $log = null
): ResponseInterface {
$stations = $this->em->getRepository(Entity\Station::class)->findAll();
$station_logs = [];
$logPaths = $this->getGlobalLogs();
foreach ($stations as $station) {
/** @var Entity\Station $station */
$station_logs[$station->getId()] = [
'name' => $station->getName(),
'logs' => $this->getStationLogs($station),
];
if (null === $log) {
$router = $request->getRouter();
return $response->withJson(
[
'logs' => array_map(
function (string $key, array $row) use ($router) {
$row['key'] = $key;
$row['links'] = [
'self' => (string)$router->named(
'api:admin:log',
[
'log' => $key,
]
),
];
return $row;
},
array_keys($logPaths),
array_values($logPaths)
),
]
);
}
return $request->getView()->renderToResponse(
if (!isset($logPaths[$log])) {
throw new Exception('Invalid log file specified.');
}
return $this->streamLogToResponse(
$request,
$response,
'admin/logs/index',
[
'global_logs' => $this->getGlobalLogs(),
'station_logs' => $station_logs,
]
$logPaths[$log]['path'],
$logPaths[$log]['tail'] ?? true
);
}
@ -85,24 +102,4 @@ final class LogsController extends AbstractLogViewerController
return $logPaths;
}
public function viewAction(
ServerRequest $request,
Response $response,
string $station_id,
string $log
): ResponseInterface {
if ('global' === $station_id) {
$log_areas = $this->getGlobalLogs();
} else {
$log_areas = $this->getStationLogs($request->getStation());
}
if (!isset($log_areas[$log])) {
throw new Exception('Invalid log file specified.');
}
$logArea = $log_areas[$log];
return $this->streamLogToResponse($request, $response, $logArea['path'], $logArea['tail'] ?? true);
}
}

View File

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Controller\Api\Traits\HasLogViewer;
use App\Entity\Station;
use App\Exception;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Radio\Enums\BackendAdapters;
use App\Radio\Enums\FrontendAdapters;
use Psr\Http\Message\ResponseInterface;
final class LogsAction
{
use HasLogViewer;
public function __invoke(
ServerRequest $request,
Response $response,
string $station_id,
?string $log = null
): ResponseInterface {
$station = $request->getStation();
$logPaths = $this->getStationLogs($station);
if (null === $log) {
$router = $request->getRouter();
return $response->withJson(
[
'logs' => array_map(
function (string $key, array $row) use ($router, $station_id) {
$row['key'] = $key;
$row['links'] = [
'self' => (string)$router->named(
'api:stations:log',
[
'station_id' => $station_id,
'log' => $key,
]
),
];
return $row;
},
array_keys($logPaths),
array_values($logPaths)
),
]
);
}
if (!isset($logPaths[$log])) {
throw new Exception('Invalid log file specified.');
}
$frontendConfig = $station->getFrontendConfig();
$filteredTerms = [
$station->getAdapterApiKey(),
$frontendConfig->getAdminPassword(),
$frontendConfig->getRelayPassword(),
$frontendConfig->getSourcePassword(),
$frontendConfig->getStreamerPassword(),
];
return $this->streamLogToResponse(
$request,
$response,
$logPaths[$log]['path'],
$logPaths[$log]['tail'] ?? true,
$filteredTerms
);
}
private function getStationLogs(Station $station): array
{
$logPaths = [];
$stationConfigDir = $station->getRadioConfigDir();
$logPaths['station_nginx'] = [
'name' => __('Station Nginx Configuration'),
'path' => $stationConfigDir . '/nginx.conf',
'tail' => false,
];
if (BackendAdapters::Liquidsoap === $station->getBackendTypeEnum()) {
$logPaths['liquidsoap_log'] = [
'name' => __('Liquidsoap Log'),
'path' => $stationConfigDir . '/liquidsoap.log',
'tail' => true,
];
$logPaths['liquidsoap_liq'] = [
'name' => __('Liquidsoap Configuration'),
'path' => $stationConfigDir . '/liquidsoap.liq',
'tail' => false,
];
}
switch ($station->getFrontendTypeEnum()) {
case FrontendAdapters::Icecast:
$logPaths['icecast_access_log'] = [
'name' => __('Icecast Access Log'),
'path' => $stationConfigDir . '/icecast_access.log',
'tail' => true,
];
$logPaths['icecast_error_log'] = [
'name' => __('Icecast Error Log'),
'path' => $stationConfigDir . '/icecast.log',
'tail' => true,
];
$logPaths['icecast_xml'] = [
'name' => __('Icecast Configuration'),
'path' => $stationConfigDir . '/icecast.xml',
'tail' => false,
];
break;
case FrontendAdapters::Shoutcast:
$logPaths['shoutcast_log'] = [
'name' => __('SHOUTcast Log'),
'path' => $stationConfigDir . '/shoutcast.log',
'tail' => true,
];
$logPaths['shoutcast_conf'] = [
'name' => __('SHOUTcast Configuration'),
'path' => $stationConfigDir . '/sc_serv.conf',
'tail' => false,
];
break;
}
return $logPaths;
}
}

View File

@ -18,7 +18,8 @@ trait HasLogViewer
ServerRequest $request,
Response $response,
string $log_path,
bool $tail_file = true
bool $tail_file = true,
array $filteredTerms = []
): ResponseInterface {
clearstatcache();
@ -28,7 +29,10 @@ trait HasLogViewer
if (!$tail_file) {
$log = file_get_contents($log_path) ?: '';
$log_contents = $this->processLog($request, $log);
$log_contents = $this->processLog(
rawLog: $log,
filteredTerms: $filteredTerms
);
return $response->withJson(
[
@ -66,7 +70,12 @@ trait HasLogViewer
$log_contents_raw = fread($fp, $log_visible_size) ?: '';
fclose($fp);
$log_contents = $this->processLog($request, $log_contents_raw, $cut_first_line, true);
$log_contents = $this->processLog(
rawLog: $log_contents_raw,
cutFirstLine: $cut_first_line,
cutEmptyLastLine: true,
filteredTerms: $filteredTerms
);
}
return $response->withJson(
@ -78,11 +87,11 @@ trait HasLogViewer
);
}
protected function processLog(
ServerRequest $request,
private function processLog(
string $rawLog,
bool $cutFirstLine = false,
bool $cutEmptyLastLine = false
bool $cutEmptyLastLine = false,
array $filteredTerms = []
): string {
$logParts = explode("\n", $rawLog);
@ -96,6 +105,8 @@ trait HasLogViewer
$logParts = str_replace(['>', '<'], ['&gt;', '&lt;'], $logParts);
$log = implode("\n", $logParts);
return mb_convert_encoding($log, 'UTF-8', 'UTF-8');
$log = mb_convert_encoding($log, 'UTF-8', 'UTF-8');
return str_replace($filteredTerms, '(PASSWORD)', $log);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Controller\Stations;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
final class HelpAction
{
public function __invoke(
ServerRequest $request,
Response $response,
string $station_id
): ResponseInterface {
$router = $request->getRouter();
return $request->getView()->renderVuePage(
response: $response,
component: 'Vue_StationsHelp',
id: 'stations-help',
title: __('Help'),
props: [
'logsUrl' => (string)$router->fromHere('api:stations:logs'),
],
);
}
}

View File

@ -1,67 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Stations;
use App\Controller\AbstractLogViewerController;
use App\Exception;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
final class LogsController extends AbstractLogViewerController
{
public function __invoke(
ServerRequest $request,
Response $response,
string $station_id
): ResponseInterface {
$station = $request->getStation();
return $request->getView()->renderToResponse($response, 'stations/logs/index', [
'logs' => $this->getStationLogs($station),
]);
}
public function viewAction(
ServerRequest $request,
Response $response,
string $station_id,
string $log,
): ResponseInterface {
$station = $request->getStation();
$log_areas = $this->getStationLogs($station);
if (!isset($log_areas[$log])) {
throw new Exception('Invalid log file specified.');
}
$logArea = $log_areas[$log];
return $this->streamLogToResponse($request, $response, $logArea['path'], $logArea['tail'] ?? true);
}
protected function processLog(
ServerRequest $request,
string $rawLog,
bool $cutFirstLine = false,
bool $cutEmptyLastLine = false
): string {
$log = parent::processLog($request, $rawLog, $cutFirstLine, $cutEmptyLastLine);
// Filter out passwords, API keys, etc.
$station = $request->getStation();
$frontendConfig = $station->getFrontendConfig();
$passwords = [
$station->getAdapterApiKey(),
$frontendConfig->getAdminPassword(),
$frontendConfig->getRelayPassword(),
$frontendConfig->getSourcePassword(),
$frontendConfig->getStreamerPassword(),
];
return str_replace($passwords, '(PASSWORD)', $log);
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* @var App\Environment $environment
*/
$this->layout('main', [
'title' => __('Log Viewer'),
'manual' => true,
]);
?>
<div class="row">
<div class="col-md-8">
<div class="card mb-3">
<div class="card-header bg-primary-dark">
<h2 class="card-title"><?=__('System Logs')?></h2>
</div>
<div class="card-body">
<?php if ($environment->isDocker()): ?>
<p><?= sprintf(
__(
'Because you are running Docker, some system logs can only be accessed from a shell session on the host computer. You can run <code>%s</code> to access container logs from the terminal.'
),
'docker-compose logs -f (nginx|web|stations|...)'
) ?></p>
<?php endif; ?>
</div>
<div class="list-group list-group-flush">
<?php foreach ($global_logs as $log_key => $log_info): ?>
<a class="list-group-item list-group-item-action log-item" href="<?=$router->fromHere('admin:logs:view',
['station_id' => 'global', 'log' => $log_key])?>">
<span class="log-name"><?=$log_info['name']?></span><br>
<small class="text-secondary"><?=$log_info['path']?></small>
</a>
<?php endforeach; ?>
</div>
</div>
<div class="card">
<div class="card-header bg-primary-dark">
<h2 class="card-title"><?=__('Logs by Station')?></h2>
</div>
<div class="card-body pb-0">
<ul class="nav nav-pills nav-pills-scrollable card-header-pills">
<?php foreach ($station_logs as $station_id => $station_row): ?>
<li class="nav-item">
<a class="nav-link" role="tab" data-toggle="tab" aria-expanded="true" aria-controls="logs_station_<?=$station_id?>" href="#logs_station_<?=$station_id?>"><?=$this->e($station_row['name'])?></a>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="tab-content">
<?php foreach ($station_logs as $station_id => $station_row): ?>
<div class="list-group list-group-flush tab-pane" id="logs_station_<?=$station_id?>">
<?php foreach ($station_row['logs'] as $log_key => $log_info): ?>
<a class="list-group-item list-group-item-action log-item" href="<?=$router->fromHere('admin:logs:view',
['station_id' => $station_id, 'log' => $log_key])?>">
<span class="log-name"><?=$log_info['name']?></span><br>
<small class="text-secondary"><?=$log_info['path']?></small>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-md-4">
<?=$this->fetch('partials/log_help_card')?>
</div>
</div>
<?=$this->fetch('partials/log_viewer')?>

View File

@ -1,24 +0,0 @@
<div class="card">
<div class="card-header bg-primary-dark">
<h2 class="card-title"><?=__('Need Help?')?></h2>
</div>
<div class="card-body">
<p><?=sprintf(
__('You can find answers for many common questions in our <a href="%s" target="_blank">support documents</a>.'),
'https://docs.azuracast.com/en/user-guide/troubleshooting'
)?></p>
<p><?=__('If you\'re experiencing a bug or error, you can submit a GitHub issue using the link below.')?></p>
<p><?=sprintf(
__('Your current installation type is <b>%s</b>. Be sure to include this when creating a new issue.'),
($environment->isDocker() ? 'Docker' : 'Ansible')
)?></p>
</div>
<div class="card-actions">
<a class="btn btn-outline-primary" role="button" href="https://github.com/AzuraCast/AzuraCast/issues/new/choose" target="_blank">
<i class="material-icons" aria-hidden="true">contact_support</i>
<?=__('Add New GitHub Issue')?>
</a>
</div>
</div>

View File

@ -1,35 +0,0 @@
$(function () {
var logUrl = $('#log-view').data('url');
var logContents = $('#log-view-contents');
var currentLogPosition, timeoutUpdateLog;
function updateLogView () {
$.getJSON(logUrl, {
position: currentLogPosition
}, function (logData) {
if (currentLogPosition == 0) {
logContents.text('');
}
if (logData.contents != '') {
logContents.append(logData.contents + '\n');
// Timeout to allow height to adjust to appended contents.
setTimeout(function () {
logContents.animate({ scrollTop: logContents.prop('scrollHeight') }, 'fast');
}, 500);
}
currentLogPosition = logData.position;
if (!logData.eof) {
timeoutUpdateLog = setTimeout(updateLogView, 15000);
}
}).fail(function (xhr) {
logContents.text('Error: ' + xhr.responseJSON.message);
});
}
timeoutUpdateLog = setTimeout(updateLogView, 1000);
});

View File

@ -1,15 +0,0 @@
<?php
/** @var \App\Assets $assets */
$assets
->load('clipboard')
->addInlineJs($this->fetch('partials/log_inline.js'), 99);
?>
<div id="log-view" data-url="<?=$url?>">
<textarea class="form-control log-viewer" id="log-view-contents" spellcheck="false" readonly>Loading...</textarea>
<div class="buttons pt-2">
<button class="btn btn-copy btn-primary btn-sm" data-clipboard-target="#log-view-contents">
<i class="material-icons">file_copy</i> <?=__('Copy to Clipboard')?>
</button>
</div>
</div>

View File

@ -1,65 +0,0 @@
$(function() {
var log_modal = $('#modal-log-view'),
log_modal_contents = $('#modal-log-view-contents');
var current_log_url,
current_log_position,
timeout_update_log;
log_modal.modal({
focus: false,
show: false
});
function updateLogView() {
$.getJSON(current_log_url, {
position: current_log_position
}, function(log_data) {
if (current_log_position == 0) {
log_modal_contents.text('');
}
if (log_data.contents != '') {
log_modal_contents.append(log_data.contents+"\n");
if (log_modal.find('#modal-log-autoscroll').is(':checked')) {
// Timeout to allow height to adjust to appended contents.
setTimeout(function() {
log_modal_contents.animate({scrollTop: log_modal_contents.prop("scrollHeight")}, "fast");
}, 500);
}
}
current_log_position = log_data.position;
if (!log_data.eof) {
timeout_update_log = setTimeout(updateLogView, 15000);
}
}).fail(function(xhr) {
log_modal_contents.text('Error: '+xhr.responseJSON.message);
});
}
log_modal.on('hide.bs.modal', function (event) {
current_log_url = null;
current_log_position = 0;
clearTimeout(timeout_update_log);
});
$('a.log-item').on('click', function(e) {
e.preventDefault();
current_log_url = $(this).attr('href');
current_log_position = 0;
log_modal.find('.modal-title').text($(this).find('.log-name').text());
log_modal.find('#modal-log-view-contents').text('Loading...');
log_modal.modal('show');
updateLogView();
return false;
});
});

View File

@ -1,34 +0,0 @@
<?php
/** @var \App\Assets $assets */
$assets
->load('clipboard')
->addInlineJs($this->fetch('partials/log_viewer.js'), 99);
?>
<div class="modal fade" id="modal-log-view" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="modal-log-view-label"><?= __('Log Viewer') ?></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="custom-control custom-switch pb-3">
<input class="custom-control-input" id="modal-log-autoscroll" type="checkbox" checked>
<span class="custom-control-track"></span>
<label class="custom-control-label" for="modal-log-autoscroll"><?= __(
'Automatically scroll to the bottom of the log'
) ?></label>
</div>
<textarea class="form-control log-viewer" id="modal-log-view-contents" spellcheck="false" readonly>Loading...</textarea>
<div class="buttons pt-2">
<button class="btn btn-copy btn-primary btn-sm" data-clipboard-target="#modal-log-view-contents">
<i class="material-icons">file_copy</i> <?=__('Copy to Clipboard')?>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,29 +0,0 @@
<?php
$this->layout('main', [
'title' => __('Log Viewer'),
'manual' => true
]);
?>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary-dark">
<h2 class="card-title"><?=__('Available Logs') ?></h2>
</div>
<div class="list-group list-group-flush">
<?php foreach($logs as $log_key => $log_info): ?>
<a class="list-group-item list-group-item-action log-item" href="<?=$router->fromHere('stations:logs:view', ['log' => $log_key]) ?>">
<span class="log-name"><?=$log_info['name'] ?></span><br>
<small class="text-secondary"><?=$log_info['path'] ?></small>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-md-4">
<?=$this->fetch('partials/log_help_card') ?>
</div>
</div>
<?=$this->fetch('partials/log_viewer') ?>