feat(admin): скрытие доменов и сохраняемый порядок колонок (#26)
This commit is contained in:
1 parent
dd6d221dd7
commit
00fbef9ede
12 files changed
+252
-33
No files matched your search
+33
-2
@@ -24,7 +24,7 @@ final class Store
|
||||
public function path(string $name): string { if (!preg_match('~^[a-zA-Z0-9_./-]+$~D',$name) || str_contains($name,'..') || str_starts_with($name,'/')) throw new Problem('Недопустимый идентификатор.'); return $this->dir.'/'.$name; }
|
||||
public function targets(bool $public=true): array {
|
||||
$targets=array_values($this->read('targets.json'));
|
||||
if ($public) foreach($targets as &$target) {$target['has_secret']=!empty($target['secret']);unset($target['secret']);}
|
||||
if ($public) foreach($targets as &$target) {$target['hidden']=(bool)($target['hidden']??false);$target['has_secret']=!empty($target['secret']);unset($target['secret']);}
|
||||
return $targets;
|
||||
}
|
||||
public function target(string $id): array {foreach($this->targets(false) as $target) if ($target['id']===$id) return $target;throw new Problem('Подключение не найдено.',404);}
|
||||
@@ -47,6 +47,7 @@ final class Store
|
||||
}
|
||||
$target['port']=(int)($input['port'] ?? $old['port'] ?? 22);if($target['port']<1||$target['port']>65535) throw new Problem('Некорректный порт.');
|
||||
$target['id']=$id;$target['enabled']=(bool)($input['enabled'] ?? $old['enabled'] ?? false);$target['secret']=$secret ?? $old['secret'] ?? '';
|
||||
$target['hidden']=(bool)($old['hidden']??false);
|
||||
// Изменение транспорта сбрасывает проверку: запись включается только после test.
|
||||
$identity=static fn($t)=>array_intersect_key($t,array_flip(['type','host','port','user','root','host_key','secret']));
|
||||
$target['verified']=isset($old['verified']) && $identity($old)===$identity($target) ? $old['verified'] : false;
|
||||
@@ -54,7 +55,37 @@ final class Store
|
||||
});
|
||||
}
|
||||
public function verifyTarget(string $id, ?array $expected=null): void {$this->locked('targets',function() use($id,$expected){$targets=$this->read('targets.json');if(!isset($targets[$id])) throw new Problem('Подключение не найдено.',404);if($expected!==null&&$targets[$id]!==$expected)throw new Problem('Подключение изменилось во время проверки. Повторите проверку.',409);$targets[$id]['verified']=true;$this->write('targets.json',$targets);});}
|
||||
public function deleteTarget(string $id): void {$this->locked('targets',function() use($id){$targets=$this->read('targets.json');unset($targets[$id]);$this->write('targets.json',$targets);});}
|
||||
public function setTargetVisibility(string $id,bool $hidden): array {
|
||||
return $this->locked('targets',function() use($id,$hidden){
|
||||
$targets=$this->read('targets.json');if(!isset($targets[$id]))throw new Problem('Подключение не найдено.',404);
|
||||
$targets[$id]['hidden']=$hidden;$this->write('targets.json',$targets);return $this->targets();
|
||||
});
|
||||
}
|
||||
public function orderTargets(array $ids): array {
|
||||
if(!array_is_list($ids))throw new Problem('Порядок должен быть списком ID.');
|
||||
foreach($ids as $id)if(!is_string($id))throw new Problem('Порядок должен содержать строковые ID.');
|
||||
return $this->locked('targets',function() use($ids){
|
||||
$targets=$this->read('targets.json');$visible=[];
|
||||
foreach($targets as $id=>$target)if(empty($target['hidden']))$visible[]=(string)$id;
|
||||
if(count($ids)!==count($visible)||count(array_unique($ids,SORT_STRING))!==count($ids)||array_diff($ids,$visible))throw new Problem('Список видимых подключений изменился. Обновите страницу и повторите.',409);
|
||||
$ordered=[];$position=0;
|
||||
foreach($targets as $id=>$target){if(empty($target['hidden'])){$id=$ids[$position++];$target=$targets[$id];}$ordered[$id]=$target;}
|
||||
$this->write('targets.json',$ordered);return $this->targets();
|
||||
});
|
||||
}
|
||||
public function deleteTarget(string $id): void {
|
||||
$this->locked('targets',function() use($id){
|
||||
$targets=$this->read('targets.json');if(!isset($targets[$id]))throw new Problem('Подключение не найдено.',404);
|
||||
if(empty($targets[$id]['hidden']))throw new Problem('Перед удалением скройте подключение.',409);
|
||||
foreach(glob($this->dir.'/jobs/*.json')?:[] as $path){
|
||||
$job=$this->read('jobs/'.basename($path));if(!in_array($job['status']??null,['queued','running'],true))continue;
|
||||
$references=array_column($job['results']??[],'target');
|
||||
if(isset($job['payload']['source']['id']))$references[]=$job['payload']['source']['id'];
|
||||
if(in_array($id,$references,true))throw new Problem('Подключение используется незавершённым заданием. Дождитесь его завершения.',409);
|
||||
}
|
||||
unset($targets[$id]);$this->write('targets.json',$targets);
|
||||
});
|
||||
}
|
||||
public function backup(string $target,string $content): string {
|
||||
$id=gmdate('YmdHis').'-'.bin2hex(random_bytes(6));$items=$this->read('backups/index.json');$items[]=['id'=>$id,'target'=>$target,'created_at'=>gmdate(DATE_ATOM)];
|
||||
$this->write('backups/'.$id.'.json',['content'=>$content]);$this->locked('backup-index',function() use($items){$current=$this->read('backups/index.json');$current[]=end($items);$this->write('backups/index.json',$current);});return $id;
|
||||
|
||||
Reference in new issue
Block a user