Merge branch 'storageareas' of github.com:ConnorTechnology/ZoneMinder into storageareas

This commit is contained in:
Isaac Connor 2018-11-29 14:38:31 -05:00
commit 4050763a93
21 changed files with 97 additions and 79 deletions

View File

@ -561,6 +561,7 @@ CREATE TABLE `Servers` (
`Port` INTEGER UNSIGNED,
`PathToIndex` TEXT,
`PathToZMS` TEXT,
`PathToApi` TEXT,
`Name` varchar(64) NOT NULL default '',
`State_Id` int(10) unsigned,
`Status` enum('Unknown','NotRunning','Running') NOT NULL default 'Unknown',

View File

@ -329,6 +329,22 @@ SET @s = (SELECT IF(
PREPARE stmt FROM @s;
EXECUTE stmt;
--
-- Add PathToApi column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'PathToApi'
) > 0,
"SELECT 'Column PathToApi already exists in Servers'",
"ALTER TABLE Servers ADD `PathToApi` TEXT AFTER `PathToZMS`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
--
-- Add Port column to Storage
--

View File

@ -3,7 +3,7 @@ Section: net
Priority: optional
Maintainer: Dmitry Smirnov <onlyjob@debian.org>
Uploaders: Vagrant Cascadian <vagrant@debian.org>
Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree
Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2
,cmake
,libx264-dev, libmp4v2-dev
,libavdevice-dev (>= 6:10~)

View File

@ -311,7 +311,7 @@ There are a number of specific reasons why processor loads can be high either by
The main causes are.
* Using a video palette other than greyscale or RGB24. This can cause a relatively minor performace hit, though still significant. Although some cameras and cards require using planar palettes ZM currently doesn't support this format internally and each frame is converted to an RGB representation prior to processing. Unless you have compelling reasons for using YUV or reduced RGB type palettes such as hitting USB transfer limits I would experiment to see if RGB24 or greyscale is quicker. Put your monitors into 'Monitor' mode so that only the capture daemons are running and monitor the process load of these (the 'zmc' processes) using top. Try it with various palettes to see if it makes a difference.
* Using a video palette other than greyscale or RGB24. This can cause a relatively minor performance hit, though still significant. Although some cameras and cards require using planar palettes ZM currently doesn't support this format internally and each frame is converted to an RGB representation prior to processing. Unless you have compelling reasons for using YUV or reduced RGB type palettes such as hitting USB transfer limits I would experiment to see if RGB24 or greyscale is quicker. Put your monitors into 'Monitor' mode so that only the capture daemons are running and monitor the process load of these (the 'zmc' processes) using top. Try it with various palettes to see if it makes a difference.
* Big image sizes. A image of 640x480 requires at least four times the processing of a 320x240 image. Experiment with different sizes to see what effect it may have. Sometimes a large image is just two interlaced smaller frames so has no real benefit anyway. This is especially true for analog cameras/cards as image height over 320 (NTSC) or 352 PAL) are invariably interlaced.
* Capture frame rates. Unless there's a compelling reason in your case there is often little benefit in running cameras at 25fps when 5-10fps would often get you results just as good. Try changing your monitor settings to limit your cameras to lower frame rates. You can still configure ZM to ignore these limits and capture as fast as possible when motion is detected.
* Run function. Obviously running in Record or Mocord modes or in Modect with lots of events generates a lot of DB and file activity and so CPU and load will increase.

View File

@ -58,7 +58,7 @@ Maximum FPS
Alarm Maximum FPS
If you have specified a Maximum FPS it may be that you dont want this limitation to apply when your monitor is recording motion or other event. This setting allows you to override the Maximum FPS value if this circumstance occurs. As with the Maximum FPS setting leaving this blank implies no limit so if you have set a maximum fps in the previous option then when an alarm occurs this limit would be ignored and ZoneMinder would capture as fast as possible for the duration of the alarm, returning to the limited value after the alarm has concluded. Equally you could set this to the same, or higher (or even lower) value than Maximum FPS for more precise control over the capture rate in the event of an alarm.
**IMPORTANT:** This field is subject to the same limitations as the Maxium FPS field. Ignoring these limitations will produce undesriable results.
**IMPORTANT:** This field is subject to the same limitations as the Maximum FPS field. Ignoring these limitations will produce undesriable results.
Reference Image Blend %ge
Each analysed image in ZoneMinder is a composite of previous images and is formed by applying the current image as a certain percentage of the previous reference image. Thus, if we entered the value of 10 here, each images part in the reference image will diminish by a factor of 0.9 each time round. So a typical reference image will be 10% the previous image, 9% the one before that and then 8.1%, 7.2%, 6.5% and so on of the rest of the way. An image will effectively vanish around 25 images later than when it was added. This blend value is what is specified here and if higher will make slower progressing events less detectable as the reference image would change more quickly. Similarly events will be deemed to be over much sooner as the reference image adapts to the new images more quickly. In signal processing terms the higher this value the steeper the event attack and decay of the signal. It depends on your particular requirements what the appropriate value would be for you but start with 10 here and adjust it (usually down) later if necessary.
@ -205,7 +205,8 @@ Warm-up Frames
Pre/Post Event Image Buffer
These options determine how many frames from before and after an event should be preserved with it. This allows you to view what happened immediately prior and subsequent to the event. A value of 10 for both of these will get you started but if you get a lot of short events and would prefer them to run together to form fewer longer ones then increase the Post Event buffer size. The pre-event buffer is a true buffer and should not really exceed half the ring buffer size. However the post-event buffer is just a count that is applied to captured frames and so can be managed more flexibly. You should also bear in mind the frame rate of the camera when choosing these values. For instance a network camera capturing at 1FPS will give you 10 seconds before and after each event if you chose 10 here. This may well be too much and pad out events more than necessary. However a fast video card may capture at 25FPS and you will want to ensure that this setting enables you to view a reasonable time frame pre and post event.
Stream Replay Image Buffer
This option ...
The number of frames buffered to allow pausing and rewinding of the stream when live viewing a monitor. A value of 0 disables the feature.
Frames are buffered to ZM_PATH_SWAP. If this path points to a physical drive, a lot of IO will be caused during live view / montage. If you experience high system load in those situations, either disable the feature or use a RAM drive for ZM_PATH_SWAP.
Alarm Frame Count
This option allows you to specify how many consecutive alarm frames must occur before an alarm event is generated. The usual, and default, value is 1 which implies that any alarm frame will cause or participate in an event. You can enter any value up to 16 here to eliminate bogus events caused perhaps by screen flickers or other transients. Values over 3 or 4 are unlikely to be useful however. Please note that if you have statistics recording enabled then currently statistics are not recorded for the first Alarm Frame Count-1 frames of an event. So if you set this value to 5 then the first 4 frames will be missing statistics whereas the more usual value of 1 will ensure that all alarm frames have statistics recorded.

View File

@ -25,15 +25,13 @@ sub GetServices {
soap_action => 'http://www.onvif.org/ver10/device/wsdl/GetServices',
style => 'document',
body => {
'use' => 'literal',
use => 'literal',
namespace => 'http://schemas.xmlsoap.org/wsdl/soap/',
encodingStyle => '',
parts => [qw( ONVIF::Device::Elements::GetServices )],
},
header => {
},
headerfault => {
@ -50,9 +48,7 @@ sub GetServiceCapabilities {
soap_action => 'http://www.onvif.org/ver10/device/wsdl/GetServiceCapabilities',
style => 'document',
body => {
'use' => 'literal',
use => 'literal',
namespace => 'http://schemas.xmlsoap.org/wsdl/soap/',
encodingStyle => '',
parts => [qw( ONVIF::Device::Elements::GetServiceCapabilities )],
@ -3059,7 +3055,7 @@ Returns a L<ONVIF::Device::Elements::SetClientCertificateModeResponse|ONVIF::Dev
=head3 GetRelayOutputs
This method has been depricated with version 2.0. Refer to the DeviceIO service.
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
Returns a L<ONVIF::Device::Elements::GetRelayOutputsResponse|ONVIF::Device::Elements::GetRelayOutputsResponse> object.
@ -3069,7 +3065,7 @@ Returns a L<ONVIF::Device::Elements::GetRelayOutputsResponse|ONVIF::Device::Elem
=head3 SetRelayOutputSettings
This method has been depricated with version 2.0. Refer to the DeviceIO service.
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
Returns a L<ONVIF::Device::Elements::SetRelayOutputSettingsResponse|ONVIF::Device::Elements::SetRelayOutputSettingsResponse> object.
@ -3085,7 +3081,7 @@ Returns a L<ONVIF::Device::Elements::SetRelayOutputSettingsResponse|ONVIF::Devic
=head3 SetRelayOutputState
This method has been depricated with version 2.0. Refer to the DeviceIO service.
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
Returns a L<ONVIF::Device::Elements::SetRelayOutputStateResponse|ONVIF::Device::Elements::SetRelayOutputStateResponse> object.

View File

@ -830,7 +830,7 @@ Returns a L<ONVIF::PTZ::Elements::SetPresetResponse|ONVIF::PTZ::Elements::SetPre
=head3 RemovePreset
Operation to remove a PTZ preset for the Node in the selected profile. The operation is supported if the PresetPosition capability exists for teh Node in the selected profile.
Operation to remove a PTZ preset for the Node in the selected profile. The operation is supported if the PresetPosition capability exists for the Node in the selected profile.
Returns a L<ONVIF::PTZ::Elements::RemovePresetResponse|ONVIF::PTZ::Elements::RemovePresetResponse> object.

View File

@ -816,7 +816,7 @@ shared_data The general mapped memory section
size The size, in bytes, of this section
valid Flag indicating whether this section has been initialised
active Flag indicating whether this monitor is active (enabled/disabled)
signal Flag indicating whether this monitor is reciving a valid signal
signal Flag indicating whether this monitor is receiving a valid signal
state The current monitor state, see the STATE constants below
last_write_index The last index, in the image buffer, that an image has been saved to
last_read_index The last index, in the image buffer, that an image has been analysed from

View File

@ -141,6 +141,7 @@ FfmpegCamera::FfmpegCamera(
video_last_pts = 0;
have_video_keyframe = false;
packetqueue = NULL;
error_count = 0;
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
@ -334,6 +335,7 @@ int FfmpegCamera::OpenFfmpeg() {
int ret;
have_video_keyframe = false;
error_count = 0;
// Open the input, not necessarily a file
#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0)
@ -745,10 +747,18 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
if ( packet.pts < -100000 ) {
// Ignore packets that have crazy negative pts. They aren't supposed to happen.
Warning("Ignore packet because pts is massively negative");
Warning("Ignore packet because pts %" PRId64 " is massively negative", packet.pts);
dumpPacket(&packet,"Ignored packet");
if ( error_count > 100 ) {
Error("Bad packet count over 100, going to close and re-open stream");
return -1;
}
error_count += 1;
continue;
}
// If we get a goot frame, decrease the error count.. We could zero it...
if ( error_count ) error_count -= 1;
int keyframe = packet.flags & AV_PKT_FLAG_KEY;
bytes += packet.size;
dumpPacket(&packet,"Captured Packet");

View File

@ -87,6 +87,7 @@ class FfmpegCamera : public Camera {
#endif
int64_t startTime;
int error_count;
public:
FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );

View File

@ -287,7 +287,7 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) {
total_bytes_to_read -= bytes_read;
} while ( total_bytes_to_read );
Debug(4, "%s", buffer);
Debug(4, buffer);
return total_bytes_read;
}

View File

@ -554,28 +554,28 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels);
}
if (audio_out_codec->supported_samplerates) {
if ( audio_out_codec->supported_samplerates ) {
int found = 0;
for (unsigned int i = 0; audio_out_codec->supported_samplerates[i];
for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i];
i++) {
if (audio_out_ctx->sample_rate ==
audio_out_codec->supported_samplerates[i]) {
if ( audio_out_ctx->sample_rate ==
audio_out_codec->supported_samplerates[i] ) {
found = 1;
break;
}
}
if (found) {
if ( found ) {
Debug(3, "Sample rate is good");
} else {
audio_out_ctx->sample_rate =
audio_out_codec->supported_samplerates[0];
Debug(1, "Sampel rate is no good, setting to (%d)",
Debug(1, "Sample rate is no good, setting to (%d)",
audio_out_codec->supported_samplerates[0]);
}
}
/* check that the encoder supports s16 pcm in */
if (!check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt)) {
if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) {
Debug(3, "Encoder does not support sample format %s, setting to FLTP",
av_get_sample_fmt_name(audio_out_ctx->sample_fmt));
audio_out_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
@ -584,11 +584,10 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->time_base =
(AVRational){1, audio_out_ctx->sample_rate};
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_parameters_from_context(audio_out_stream->codecpar,
audio_out_ctx);
if (ret < 0) {
if ( ret < 0 ) {
Error("Could not initialize stream parameteres");
return false;
}
@ -616,13 +615,13 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->channel_layout, audio_out_ctx->frame_size);
/** Create a new frame to store the audio samples. */
if (!(in_frame = zm_av_frame_alloc())) {
if ( !(in_frame = zm_av_frame_alloc()) ) {
Error("Could not allocate in frame");
return false;
}
/** Create a new frame to store the audio samples. */
if (!(out_frame = zm_av_frame_alloc())) {
if ( !(out_frame = zm_av_frame_alloc()) ) {
Error("Could not allocate out frame");
av_frame_free(&in_frame);
return false;
@ -630,13 +629,13 @@ bool VideoStore::setup_resampler() {
// Setup the audio resampler
resample_ctx = avresample_alloc_context();
if (!resample_ctx) {
Error("Could not allocate resample ctx\n");
if ( !resample_ctx ) {
Error("Could not allocate resample ctx");
return false;
}
// Some formats (i.e. WAV) do not produce the proper channel layout
if (audio_in_ctx->channel_layout == 0) {
if ( audio_in_ctx->channel_layout == 0 ) {
uint64_t layout = av_get_channel_layout("mono");
av_opt_set_int(resample_ctx, "in_channel_layout",
av_get_channel_layout("mono"), 0);
@ -664,38 +663,11 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->channels, 0);
ret = avresample_open(resample_ctx);
if (ret < 0) {
Error("Could not open resample ctx\n");
if ( ret < 0 ) {
Error("Could not open resample ctx");
return false;
}
#if 0
/**
* Allocate as many pointers as there are audio channels.
* Each pointer will later point to the audio samples of the corresponding
* channels (although it may be NULL for interleaved formats).
*/
if (!( converted_in_samples = reinterpret_cast<uint8_t *>calloc( audio_out_ctx->channels, sizeof(*converted_in_samples))) ) {
Error("Could not allocate converted in sample pointers\n");
return;
}
/**
* Allocate memory for the samples of all channels in one consecutive
* block for convenience.
*/
if ( (ret = av_samples_alloc( &converted_in_samples, NULL,
audio_out_ctx->channels,
audio_out_ctx->frame_size,
audio_out_ctx->sample_fmt, 0)) < 0 ) {
Error("Could not allocate converted in samples (error '%s')\n",
av_make_error_string(ret).c_str());
av_freep(converted_in_samples);
free(converted_in_samples);
return;
}
#endif
out_frame->nb_samples = audio_out_ctx->frame_size;
out_frame->format = audio_out_ctx->sample_fmt;
out_frame->channel_layout = audio_out_ctx->channel_layout;
@ -707,17 +679,17 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->sample_fmt, 0);
converted_in_samples = (uint8_t *)av_malloc(audioSampleBuffer_size);
if (!converted_in_samples) {
Error("Could not allocate converted in sample pointers\n");
if ( !converted_in_samples ) {
Error("Could not allocate converted in sample pointers");
return false;
}
// Setup the data pointers in the AVFrame
if (avcodec_fill_audio_frame(out_frame, audio_out_ctx->channels,
if ( avcodec_fill_audio_frame(out_frame, audio_out_ctx->channels,
audio_out_ctx->sample_fmt,
(const uint8_t *)converted_in_samples,
audioSampleBuffer_size, 0) < 0) {
Error("Could not allocate converted in sample pointers\n");
Error("Could not allocate converted in sample pointers");
return false;
}

View File

@ -76,7 +76,7 @@ fi;
if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then
DISTROS="xenial,bionic,cosmic,trusty"
DISTROS="xenial,bionic,cosmic,disco,trusty"
else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi;

View File

@ -221,11 +221,11 @@ class Event {
null);
if ( $this->{'DefaultVideo'} and $args['mode'] != 'jpeg' ) {
$streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php';
$streamSrc .= $Server->PathToIndex();
$args['eid'] = $this->{'Id'};
$args['view'] = 'view_video';
} else {
$streamSrc .= ZM_PATH_ZMS;
$streamSrc .= $Server->PathToZMS();
$args['source'] = 'event';
$args['event'] = $this->{'Id'};
@ -340,12 +340,11 @@ class Event {
} else {
$Server = new Server();
}
$streamSrc .= $Server->Url(
$streamSrc .= $Server->UrlToIndex(
ZM_MIN_STREAMING_PORT ?
ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} :
null);
$streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php';
$args['eid'] = $this->{'Id'};
$args['fid'] = 'snapshot';
$args['view'] = 'image';
@ -573,7 +572,7 @@ class Event {
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
if ( $Server->Id() != ZM_SERVER_ID ) {
$url = $Server->Url() . '/zm/api/events/'.$this->{'Id'}.'.json';
$url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
@ -617,7 +616,7 @@ class Event {
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
if ( $Server->Id() != ZM_SERVER_ID ) {
$url = $Server->Url() . '/zm/api/events/'.$this->{'Id'}.'.json';
$url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );

View File

@ -282,13 +282,11 @@ private $control_fields = array(
public function getStreamSrc($args, $querySep='&amp;') {
$streamSrc = $this->Server()->Url(
$streamSrc = $this->Server()->UrlToZMS(
ZM_MIN_STREAMING_PORT ?
ZM_MIN_STREAMING_PORT+$this->{'Id'} :
null);
$streamSrc .= ZM_PATH_ZMS;
$args['monitor'] = $this->{'Id'};
if ( ZM_OPT_USE_AUTH ) {
@ -638,8 +636,8 @@ private $control_fields = array(
return $source;
} // end function Source
public function Url() {
return $this->Server()->Url( ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null );
public function UrlToIndex() {
return $this->Server()->UrlToIndex(ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null);
}
} // end class Monitor

View File

@ -10,7 +10,9 @@ class Server {
'Protocol' => '',
'Hostname' => '',
'Port' => null,
'PathPrefix' => '/zm',
'PathToIndex' => '/zm',
'PathToZMS' => ZM_PATH_ZMS,
'PathToApi' => '/zm/api',
'zmaudit' => 1,
'zmstats' => 1,
'zmtrigger' => 0,
@ -81,6 +83,7 @@ class Server {
return ZM_PATH_ZMS;
}
}
public function UrlToZMS( ) {
return $this->Url().$this->PathToZMS();
}
@ -114,6 +117,18 @@ class Server {
public function UrlToIndex( ) {
return $this->Url().$this->PathToIndex();
}
public function UrlToApi( ) {
return $this->Url().$this->PathToApi();
}
public function PathToApi( $new = null ) {
if ( $new != null )
$this->{'PathToApi'} = $new;
if ( isset($this->{'PathToApi'}) and $this->{'PathToApi'} ) {
return $this->{'PathToApi'};
}
return '/zm/api';
}
public function __call($fn, array $args){
if ( count($args) ) {

View File

@ -588,6 +588,7 @@ $SLANG = array(
'PasswordsDifferent' => 'The new and confirm passwords are different',
'PathToIndex' => 'Path To Index',
'PathToZMS' => 'Path To ZMS',
'PathToApi' => 'Path To Api',
'Paths' => 'Paths',
'Pause' => 'Pause',
'PhoneBW' => 'Phone&nbsp;B/W',

View File

@ -150,6 +150,7 @@ input,textarea,select,button,.btn-primary {
font-weight: 400;
font-size: 100%;
color: #333333;
background-color: #f8f8f8;
text-align: left;
border-radius:4px;
}

View File

@ -85,7 +85,8 @@ input,textarea,select,button {
border: 1px #7f7fb2 solid;
font-family: inherit;
font-size: 100%;
color: #333333;
color: #333333;
background-color: #eeeeee;
}
input[type=text], input[type=password], textarea {

View File

@ -211,6 +211,7 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
<th class="colUrl"><?php echo translate('Url') ?></th>
<th class="colPathToIndex"><?php echo translate('PathToIndex') ?></th>
<th class="colPathToZMS"><?php echo translate('PathToZMS') ?></th>
<th class="colPathToApi"><?php echo translate('PathToApi') ?></th>
<th class="colStatus"><?php echo translate('Status') ?></th>
<th class="colMonitorCount"><?php echo translate('Monitors') ?></th>
<th class="colCpuLoad"><?php echo translate('CpuLoad') ?></th>
@ -232,6 +233,7 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
<td class="colUrl"><?php echo makePopupLink('?view=server&amp;id='.$Server->Id(), 'zmServer', 'server', validHtmlStr($Server->Url()), $canEdit) ?></td>
<td class="colPathToIndex"><?php echo makePopupLink('?view=server&amp;id='.$Server->Id(), 'zmServer', 'server', validHtmlStr($Server->PathToIndex()), $canEdit) ?></td>
<td class="colPathToZMS"><?php echo makePopupLink('?view=server&amp;id='.$Server->Id(), 'zmServer', 'server', validHtmlStr($Server->PathToZMS()), $canEdit) ?></td>
<td class="colPathToApi"><?php echo makePopupLink('?view=server&amp;id='.$Server->Id(), 'zmServer', 'server', validHtmlStr($Server->PathToApi()), $canEdit) ?></td>
<td class="colStatus <?php if ( $Server->Status() == 'NotRunning' ) { echo 'danger'; } ?>">
<?php echo makePopupLink('?view=server&amp;id='.$Server->Id(), 'zmServer', 'server', validHtmlStr($Server->Status()), $canEdit) ?></td>
<td class="colMonitorCount">

View File

@ -69,6 +69,10 @@ xhtmlHeaders(__FILE__, translate('Server').' - '.$Server->Name());
<th scope="row"><?php echo translate('PathToZMS') ?></th>
<td><input type="text" name="newServer[PathToZMS]" value="<?php echo $Server->PathToZMS() ?>"/></td>
</tr>
<tr>
<th scope="row"><?php echo translate('PathToApi') ?></th>
<td><input type="text" name="newServer[PathToApi]" value="<?php echo $Server->PathToApi() ?>"/></td>
</tr>
<tr>
<th scope="row"><?php echo translate('RunStats') ?></th>
<td>